1 /*< ilib.js */ 2 /* 3 * ilib.js - define the ilib name space 4 * 5 * Copyright © 2012-2015, JEDLSoft 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 /** 22 * @namespace The global namespace that contains general ilib functions useful 23 * to all of ilib 24 * 25 * @version "12.0.003" 26 */ 27 var ilib = ilib || {}; 28 29 /** @private */ 30 ilib._ver = function() { 31 return "12.0.003" 32 ; 33 }; 34 35 /** 36 * Return the current version of ilib. 37 * 38 * @static 39 * @return {string} a version string for this instance of ilib 40 */ 41 ilib.getVersion = function () { 42 // TODO: need some way of getting the version number under dynamic load code 43 return ilib._ver() || "12.0"; 44 }; 45 46 /** 47 * Place where resources and such are eventually assigned. 48 */ 49 ilib.data = { 50 /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */ 51 norm: { 52 ccc: {}, 53 nfd: {}, 54 nfc: {}, 55 nfkd: {}, 56 nfkc: {} 57 }, 58 zoneinfo: { 59 "Etc/UTC":{"o":"0:0","f":"UTC"}, 60 "local":{"f":"local"} 61 }, 62 /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {}, 63 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null, 64 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null, 65 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null, 66 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null, 67 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null, 68 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null, 69 /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null, 70 /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null, 71 /** @type {null|Array.<string>} */ timezones: [] 72 }; 73 74 /* 75 if (typeof(window) !== 'undefined') { 76 window["ilib"] = ilib; 77 } 78 */ 79 80 // export ilib for use as a module in nodejs 81 if (typeof(module) !== 'undefined') { 82 83 module.exports.ilib = ilib; // for backwards compatibility with older versions of ilib 84 } 85 86 /** 87 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing 88 * internationalization aspects of software. Instead of translating the text of the software 89 * into a foreign language, as in the process of localization, the textual elements of an application 90 * are replaced with an altered version of the original language.These specific alterations make 91 * the original words appear readable, but include the most problematic characteristics of 92 * the world's languages: varying length of text or characters, language direction, and so on. 93 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF 94 * 95 * @param {string|undefined|null} localename the locale specifier for the pseudo locale 96 */ 97 ilib.setAsPseudoLocale = function (localename) { 98 if (localename) { 99 ilib.pseudoLocales.push(localename) 100 } 101 }; 102 103 /** 104 * Reset the list of pseudo locales back to the default single locale of zxx-XX. 105 * @static 106 */ 107 ilib.clearPseudoLocales = function() { 108 ilib.pseudoLocales = [ 109 "zxx-XX", 110 "zxx-Cyrl-XX", 111 "zxx-Hans-XX", 112 "zxx-Hebr-XX" 113 ]; 114 }; 115 116 ilib.clearPseudoLocales(); 117 118 /** 119 * Return the name of the platform 120 * @private 121 * @static 122 * @return {string} string naming the platform 123 */ 124 ilib._getPlatform = function () { 125 if (!ilib._platform) { 126 try { 127 if (typeof(java.lang.Object) !== 'undefined') { 128 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; 129 return ilib._platform; 130 } 131 } catch (e) {} 132 133 if (typeof(process) !== 'undefined' && typeof(module) !== 'undefined') { 134 ilib._platform = "nodejs"; 135 } else if (typeof(Qt) !== 'undefined') { 136 ilib._platform = "qt"; 137 } else if (typeof(window) !== 'undefined') { 138 ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser"; 139 } else { 140 ilib._platform = "unknown"; 141 } 142 } 143 return ilib._platform; 144 }; 145 146 /** 147 * If this ilib is running in a browser, return the name of that browser. 148 * @private 149 * @static 150 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", 151 * "safari", or "opera"), or undefined if this is not running in a browser or if 152 * the browser name could not be determined 153 */ 154 ilib._getBrowser = function () { 155 var browser = undefined; 156 if (ilib._getPlatform() === "browser") { 157 if (navigator && navigator.userAgent) { 158 if (navigator.userAgent.indexOf("Firefox") > -1) { 159 browser = "firefox"; 160 } 161 if (navigator.userAgent.indexOf("Opera") > -1) { 162 browser = "opera"; 163 } 164 if (navigator.userAgent.indexOf("Chrome") > -1) { 165 browser = "chrome"; 166 } 167 if (navigator.userAgent.indexOf(" .NET") > -1) { 168 browser = "ie"; 169 } 170 if (navigator.userAgent.indexOf("Safari") > -1) { 171 // chrome also has the string Safari in its userAgent, but the chrome case is 172 // already taken care of above 173 browser = "safari"; 174 } 175 } 176 } 177 return browser; 178 }; 179 180 /** 181 * Return the value of a global variable given its name in a way that works 182 * correctly for the current platform. 183 * @private 184 * @static 185 * @param {string} name the name of the variable to return 186 * @return {*} the global variable, or undefined if it does not exist 187 */ 188 ilib._global = function(name) { 189 switch (ilib._getPlatform()) { 190 case "rhino": 191 var top = (function() { 192 return (typeof global === 'object') ? global : this; 193 })(); 194 break; 195 case "nodejs": 196 case "trireme": 197 top = typeof(global) !== 'undefined' ? global : this; 198 //console.log("ilib._global: top is " + (typeof(global) !== 'undefined' ? "global" : "this")); 199 break; 200 case "qt": 201 return undefined; 202 default: 203 top = window; 204 break; 205 } 206 try { 207 return top[name]; 208 } catch (e) { 209 return undefined; 210 } 211 }; 212 213 /** 214 * Return true if the global variable is defined on this platform. 215 * @private 216 * @static 217 * @param {string} name the name of the variable to check 218 * @return {boolean} true if the global variable is defined on this platform, false otherwise 219 */ 220 ilib._isGlobal = function(name) { 221 return typeof(ilib._global(name)) !== 'undefined'; 222 }; 223 224 /** 225 * Sets the default locale for all of ilib. This locale will be used 226 * when no explicit locale is passed to any ilib class. If the default 227 * locale is not set, ilib will attempt to use the locale of the 228 * environment it is running in, if it can find that. If not, it will 229 * default to the locale "en-US". If a type of parameter is string, 230 * ilib will take only well-formed BCP-47 tag <p> 231 * 232 * 233 * @static 234 * @param {string|undefined|null} spec the locale specifier for the default locale 235 */ 236 ilib.setLocale = function (spec) { 237 if (typeof(spec) === 'string' || !spec) { 238 ilib.locale = spec; 239 } 240 // else ignore other data types, as we don't have the dependencies 241 // to look into them to find a locale 242 }; 243 244 /** 245 * Return the default locale for all of ilib if one has been set. This 246 * locale will be used when no explicit locale is passed to any ilib 247 * class. If the default 248 * locale is not set, ilib will attempt to use the locale of the 249 * environment it is running in, if it can find that. If not, it will 250 * default to the locale "en-US".<p> 251 * 252 * 253 * @static 254 * @return {string} the locale specifier for the default locale 255 */ 256 ilib.getLocale = function () { 257 if (typeof(ilib.locale) !== 'string') { 258 var plat = ilib._getPlatform(); 259 switch (plat) { 260 case 'browser': 261 // running in a browser 262 ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit 263 if (!ilib.locale) { 264 // IE on Windows 265 var lang = typeof(navigator.browserLanguage) !== 'undefined' ? 266 navigator.browserLanguage : 267 (typeof(navigator.userLanguage) !== 'undefined' ? 268 navigator.userLanguage : 269 (typeof(navigator.systemLanguage) !== 'undefined' ? 270 navigator.systemLanguage : 271 undefined)); 272 if (typeof(lang) !== 'undefined' && lang) { 273 // for some reason, MS uses lower case region tags 274 ilib.locale = lang.substring(0,3) + lang.substring(3,5).toUpperCase(); 275 } 276 } 277 break; 278 case 'webos': 279 // webOS 280 if (typeof(PalmSystem.locales) !== 'undefined' && 281 typeof(PalmSystem.locales.UI) != 'undefined' && 282 PalmSystem.locales.UI.length > 0) { 283 ilib.locale = PalmSystem.locales.UI; 284 } else if (typeof(PalmSystem.locale) !== 'undefined') { 285 ilib.locale = PalmSystem.locale; 286 } 287 break; 288 case 'rhino': 289 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) { 290 // running under plain rhino 291 ilib.locale = environment.user.language; 292 if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) { 293 ilib.locale += '-' + environment.user.country; 294 } 295 } 296 break; 297 case "trireme": 298 // under trireme on rhino emulating nodejs 299 var lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL; 300 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 301 // where language and region are the correct ISO codes separated by 302 // an underscore. This translate it back to the BCP-47 form. 303 if (lang && typeof(lang) !== 'undefined') { 304 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 305 } 306 break; 307 case 'nodejs': 308 // running under nodejs 309 var lang = process.env.LANG || process.env.LC_ALL; 310 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 311 // where language and region are the correct ISO codes separated by 312 // an underscore. This translate it back to the BCP-47 form. 313 if (lang && typeof(lang) !== 'undefined') { 314 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 315 } 316 break; 317 case 'qt': 318 // running in the Javascript engine under Qt/QML 319 var locobj = Qt.locale(); 320 var lang = locobj.name && locobj.name.replace("_", "-") || "en-US"; 321 break; 322 } 323 ilib.locale = typeof(ilib.locale) === 'string' ? ilib.locale : 'en-US'; 324 } 325 return ilib.locale; 326 }; 327 328 /** 329 * Sets the default time zone for all of ilib. This time zone will be used when 330 * no explicit time zone is passed to any ilib class. If the default time zone 331 * is not set, ilib will attempt to use the time zone of the 332 * environment it is running in, if it can find that. If not, it will 333 * default to the the UTC zone "Etc/UTC".<p> 334 * 335 * 336 * @static 337 * @param {string} tz the name of the time zone to set as the default time zone 338 */ 339 ilib.setTimeZone = function (tz) { 340 ilib.tz = tz || ilib.tz; 341 }; 342 343 /** 344 * Return the default time zone for all of ilib if one has been set. This 345 * time zone will be used when no explicit time zone is passed to any ilib 346 * class. If the default time zone 347 * is not set, ilib will attempt to use the locale of the 348 * environment it is running in, if it can find that. If not, it will 349 * default to the the zone "local".<p> 350 * 351 * 352 * @static 353 * @return {string} the default time zone for ilib 354 */ 355 ilib.getTimeZone = function() { 356 if (typeof(ilib.tz) === 'undefined') { 357 if (typeof(navigator) !== 'undefined' && typeof(navigator.timezone) !== 'undefined') { 358 // running in a browser 359 if (navigator.timezone.length > 0) { 360 ilib.tz = navigator.timezone; 361 } 362 } else if (typeof(PalmSystem) !== 'undefined' && typeof(PalmSystem.timezone) !== 'undefined') { 363 // running in webkit on webOS 364 if (PalmSystem.timezone.length > 0) { 365 ilib.tz = PalmSystem.timezone; 366 } 367 } else if (typeof(environment) !== 'undefined' && typeof(environment.user) !== 'undefined') { 368 // running under rhino 369 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) { 370 ilib.tz = environment.user.timezone; 371 } 372 } else if (typeof(process) !== 'undefined' && typeof(process.env) !== 'undefined') { 373 // running in nodejs 374 if (process.env.TZ && typeof(process.env.TZ) !== "undefined") { 375 ilib.tz = process.env.TZ; 376 } 377 } 378 379 ilib.tz = ilib.tz || "local"; 380 } 381 382 return ilib.tz; 383 }; 384 385 /** 386 * @class 387 * Defines the interface for the loader class for ilib. The main method of the 388 * loader object is loadFiles(), which loads a set of requested locale data files 389 * from where-ever it is stored. 390 * @interface 391 */ 392 ilib.Loader = function() {}; 393 394 /** 395 * Load a set of files from where-ever it is stored.<p> 396 * 397 * This is the main function define a callback function for loading missing locale 398 * data or resources. 399 * If this copy of ilib is assembled without including the required locale data 400 * or resources, then that data can be lazy loaded dynamically when it is 401 * needed by calling this method. Each ilib class will first 402 * check for the existence of data under ilib.data, and if it is not there, 403 * it will attempt to load it by calling this method of the laoder, and then place 404 * it there.<p> 405 * 406 * Suggested implementations of this method might load files 407 * directly from disk under nodejs or rhino, or within web pages, to load 408 * files from the server with XHR calls.<p> 409 * 410 * The first parameter to this method, paths, is an array of relative paths within 411 * the ilib dir structure for the 412 * requested data. These paths will already have the locale spec integrated 413 * into them, so no further tweaking needs to happen to load the data. Simply 414 * load the named files. The second 415 * parameter tells the loader whether to load the files synchronously or asynchronously. 416 * If the sync parameters is false, then the onLoad function must also be specified. 417 * The third parameter gives extra parameters to the loader passed from the calling 418 * code. This may contain any property/value pairs. The last parameter, callback, 419 * is a callback function to call when all of the data is finishing loading. Make 420 * sure to call the callback with the context of "this" so that the caller has their 421 * context back again.<p> 422 * 423 * The loader function must be able to operate either synchronously or asychronously. 424 * If the loader function is called with an undefined callback function, it is 425 * expected to load the data synchronously, convert it to javascript 426 * objects, and return the array of json objects as the return value of the 427 * function. If the loader 428 * function is called with a callback function, it may load the data 429 * synchronously or asynchronously (doesn't matter which) as long as it calls 430 * the callback function with the data converted to a javascript objects 431 * when it becomes available. If a particular file could not be loaded, the 432 * loader function should put undefined into the corresponding entry in the 433 * results array. 434 * Note that it is important that all the data is loaded before the callback 435 * is called.<p> 436 * 437 * An example implementation for nodejs might be: 438 * 439 * <pre> 440 * * 441 * var myLoader = function() {}; 442 * myLoader.prototype = new Loader(); 443 * myLoader.prototype.constructor = myLoader; 444 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) { 445 * if (sync) { 446 * var ret = []; 447 * // synchronous load -- just return the result 448 * paths.forEach(function (path) { 449 * var json = fs.readFileSync(path, "utf-8"); 450 * ret.push(json ? JSON.parse(json) : undefined); 451 * }); 452 * 453 * return ret; 454 * } 455 * this.callback = callback; 456 * 457 * // asynchronous 458 * this.results = []; 459 * this._loadFilesAsync(paths); 460 * } 461 * myLoader.prototype._loadFilesAsync = function (paths) { 462 * if (paths.length > 0) { 463 * var file = paths.shift(); 464 * fs.readFile(file, "utf-8", function(err, json) { 465 * this.results.push(err ? undefined : JSON.parse(json)); 466 * // call self recursively so that the callback is only called at the end 467 * // when all the files are loaded sequentially 468 * if (paths.length > 0) { 469 * this._loadFilesAsync(paths); 470 * } else { 471 * this.callback(this.results); 472 * } 473 * }); 474 * } 475 * } 476 * 477 * // bind to "this" so that "this" is relative to your own instance 478 * ilib.setLoaderCallback(new myLoader()); 479 * </pre> 480 481 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored 482 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously 483 * @param {Object} params an object with any extra parameters for the loader. These can be 484 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that 485 * calls ilib and the code that provides the loader are together and can have a private 486 * agreement between them about what the parameters should contain. 487 * @param {function(Object)} callback function to call when the files are all loaded. The 488 * parameter of the callback function is the contents of the files. 489 */ 490 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; 491 492 /** 493 * Return all files available for loading using this loader instance. 494 * This method returns an object where the properties are the paths to 495 * directories where files are loaded from and the values are an array 496 * of strings containing the relative paths under the directory of each 497 * file that can be loaded.<p> 498 * 499 * Example: 500 * <pre> 501 * { 502 * "/usr/share/javascript/ilib/locale": [ 503 * "dateformats.json", 504 * "aa/dateformats.json", 505 * "af/dateformats.json", 506 * "agq/dateformats.json", 507 * "ak/dateformats.json", 508 * ... 509 * "zxx/dateformats.json" 510 * ] 511 * } 512 * </pre> 513 * @returns {Object} a hash containing directory names and 514 * paths to file that can be loaded by this loader 515 */ 516 ilib.Loader.prototype.listAvailableFiles = function() {}; 517 518 /** 519 * Return true if the file in the named path is available for loading using 520 * this loader. The path may be given as an absolute path, in which case 521 * only that file is checked, or as a relative path, in which case, the 522 * relative path may appear underneath any of the directories that the loader 523 * knows about. 524 * @returns {boolean} true if the file in the named path is available for loading, and 525 * false otherwise 526 */ 527 ilib.Loader.prototype.isAvailable = function(path) {}; 528 529 /** 530 * Set the custom loader used to load ilib's locale data in your environment. 531 * The instance passed in must implement the Loader interface. See the 532 * Loader class documentation for more information about loaders. 533 * 534 * @static 535 * @param {ilib.Loader} loader class to call to access the requested data. 536 * @return {boolean} true if the loader was installed correctly, or false 537 * if not 538 */ 539 ilib.setLoaderCallback = function(loader) { 540 // only a basic check 541 if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || 542 typeof(loader) === 'function' || typeof(loader) === 'undefined') { 543 //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); 544 ilib._load = loader; 545 return true; 546 } 547 return false; 548 }; 549 550 /** 551 * Return the custom Loader instance currently in use with this instance 552 * of ilib. If there is no loader, this method returns undefined. 553 * 554 * @protected 555 * @static 556 * @return {ilib.Loader|undefined} the loader instance currently in use, or 557 * undefined if there is no such loader 558 */ 559 ilib.getLoader = function() { 560 return ilib._load; 561 }; 562 563 /** 564 * Test whether an object is an javascript array. 565 * 566 * @static 567 * @param {*} object The object to test 568 * @return {boolean} return true if the object is an array 569 * and false otherwise 570 */ 571 ilib.isArray = function(object) { 572 if (typeof(object) === 'object') { 573 return Object.prototype.toString.call(object) === '[object Array]'; 574 } 575 return false; 576 }; 577 578 /** 579 * Extend object1 by mixing in everything from object2 into it. The objects 580 * are deeply extended, meaning that this method recursively descends the 581 * tree in the objects and mixes them in at each level. Arrays are extended 582 * by concatenating the elements of object2 onto those of object1. 583 * 584 * @static 585 * @param {Object} object1 the target object to extend 586 * @param {Object=} object2 the object to mix in to object1 587 * @return {Object} returns object1 588 */ 589 ilib.extend = function (object1, object2) { 590 var prop = undefined; 591 if (object2) { 592 for (prop in object2) { 593 // don't extend object with undefined or functions 594 if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { 595 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 596 //console.log("Merging array prop " + prop); 597 object1[prop] = object1[prop].concat(object2[prop]); 598 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 599 //console.log("Merging object prop " + prop); 600 if (prop !== "ilib") { 601 object1[prop] = ilib.extend(object1[prop], object2[prop]); 602 } 603 } else { 604 //console.log("Copying prop " + prop); 605 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 606 object1[prop] = object2[prop]; 607 } 608 } 609 } 610 } 611 return object1; 612 }; 613 614 ilib.extend2 = function (object1, object2) { 615 var prop = undefined; 616 if (object2) { 617 for (prop in object2) { 618 // don't extend object with undefined or functions 619 if (prop && typeof(object2[prop]) !== 'undefined') { 620 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 621 //console.log("Merging array prop " + prop); 622 object1[prop] = object1[prop].concat(object2[prop]); 623 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 624 //console.log("Merging object prop " + prop); 625 if (prop !== "ilib") { 626 object1[prop] = ilib.extend2(object1[prop], object2[prop]); 627 } 628 } else { 629 //console.log("Copying prop " + prop); 630 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 631 object1[prop] = object2[prop]; 632 } 633 } 634 } 635 } 636 return object1; 637 }; 638 639 /** 640 * If Function.prototype.bind does not exist in this JS engine, this 641 * function reimplements it in terms of older JS functions. 642 * bind() doesn't exist in many older browsers. 643 * 644 * @static 645 * @param {Object} scope object that the method should operate on 646 * @param {function(...)} method method to call 647 * @return {function(...)|undefined} function that calls the given method 648 * in the given scope with all of its arguments properly attached, or 649 * undefined if there was a problem with the arguments 650 */ 651 ilib.bind = function(scope, method/*, bound arguments*/){ 652 if (!scope || !method) { 653 return undefined; 654 } 655 656 /** @protected 657 * @param {Arguments} inArrayLike 658 * @param {number=} inOffset 659 */ 660 function cloneArray(inArrayLike, inOffset) { 661 var arr = []; 662 for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){ 663 arr.push(inArrayLike[i]); 664 } 665 return arr; 666 } 667 668 if (typeof(method) === 'function') { 669 var func, args = cloneArray(arguments, 2); 670 if (typeof(method.bind) === 'function') { 671 func = method.bind.apply(method, [scope].concat(args)); 672 } else { 673 func = function() { 674 var nargs = cloneArray(arguments); 675 // invoke with collected args 676 return method.apply(scope, args.concat(nargs)); 677 }; 678 } 679 return func; 680 } 681 return undefined; 682 }; 683 684 /** 685 * @private 686 */ 687 ilib._dyncode = false; 688 689 /** 690 * Return true if this copy of ilib is using dynamically loaded code. It returns 691 * false for pre-assembled code. 692 * 693 * @static 694 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise 695 */ 696 ilib.isDynCode = function() { 697 return ilib._dyncode; 698 }; 699 700 /** 701 * @private 702 */ 703 ilib._dyndata = false; 704 705 /** 706 * Return true if this copy of ilib is using dynamically loaded locale data. It returns 707 * false for pre-assembled data. 708 * 709 * @static 710 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise 711 */ 712 ilib.isDynData = function() { 713 return ilib._dyndata; 714 }; 715 716 ilib._loadtime = new Date().getTime(); 717 /*< JSUtils.js */ 718 /* 719 * JSUtils.js - Misc utilities to work around Javascript engine differences 720 * 721 * Copyright © 2013-2015, JEDLSoft 722 * 723 * Licensed under the Apache License, Version 2.0 (the "License"); 724 * you may not use this file except in compliance with the License. 725 * You may obtain a copy of the License at 726 * 727 * http://www.apache.org/licenses/LICENSE-2.0 728 * 729 * Unless required by applicable law or agreed to in writing, software 730 * distributed under the License is distributed on an "AS IS" BASIS, 731 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 732 * 733 * See the License for the specific language governing permissions and 734 * limitations under the License. 735 */ 736 737 // !depends ilib.js 738 739 740 var JSUtils = {}; 741 742 /** 743 * Perform a shallow copy of the source object to the target object. This only 744 * copies the assignments of the source properties to the target properties, 745 * but not recursively from there.<p> 746 * 747 * 748 * @static 749 * @param {Object} source the source object to copy properties from 750 * @param {Object} target the target object to copy properties into 751 */ 752 JSUtils.shallowCopy = function (source, target) { 753 var prop = undefined; 754 if (source && target) { 755 for (prop in source) { 756 if (prop !== undefined && typeof(source[prop]) !== 'undefined') { 757 target[prop] = source[prop]; 758 } 759 } 760 } 761 }; 762 763 /** 764 * Perform a recursive deep copy from the "from" object to the "deep" object. 765 * 766 * @static 767 * @param {Object} from the object to copy from 768 * @param {Object} to the object to copy to 769 * @return {Object} a reference to the the "to" object 770 */ 771 JSUtils.deepCopy = function(from, to) { 772 var prop; 773 774 for (prop in from) { 775 if (prop) { 776 if (typeof(from[prop]) === 'object') { 777 to[prop] = {}; 778 JSUtils.deepCopy(from[prop], to[prop]); 779 } else { 780 to[prop] = from[prop]; 781 } 782 } 783 } 784 return to; 785 }; 786 787 /** 788 * Map a string to the given set of alternate characters. If the target set 789 * does not contain a particular character in the input string, then that 790 * character will be copied to the output unmapped. 791 * 792 * @static 793 * @param {string} str a string to map to an alternate set of characters 794 * @param {Array.<string>|Object} map a mapping to alternate characters 795 * @return {string} the source string where each character is mapped to alternate characters 796 */ 797 JSUtils.mapString = function (str, map) { 798 var mapped = ""; 799 if (map && str) { 800 for (var i = 0; i < str.length; i++) { 801 var c = str.charAt(i); // TODO use a char iterator? 802 mapped += map[c] || c; 803 } 804 } else { 805 mapped = str; 806 } 807 return mapped; 808 }; 809 810 /** 811 * Check if an object is a member of the given array. If this javascript engine 812 * support indexOf, it is used directly. Otherwise, this function implements it 813 * itself. The idea is to make sure that you can use the quick indexOf if it is 814 * available, but use a slower implementation in older engines as well. 815 * 816 * @static 817 * @param {Array.<Object>} array array to search 818 * @param {Object} obj object being sought. This should be of the same type as the 819 * members of the array being searched. If not, this function will not return 820 * any results. 821 * @return {number} index of the object in the array, or -1 if it is not in the array. 822 */ 823 JSUtils.indexOf = function(array, obj) { 824 if (!array || !obj) { 825 return -1; 826 } 827 if (typeof(array.indexOf) === 'function') { 828 return array.indexOf(obj); 829 } else { 830 for (var i = 0; i < array.length; i++) { 831 if (array[i] === obj) { 832 return i; 833 } 834 } 835 return -1; 836 } 837 }; 838 839 /** 840 * Pad the str with zeros to the given length of digits. 841 * 842 * @static 843 * @param {string|number} str the string or number to pad 844 * @param {number} length the desired total length of the output string, padded 845 * @param {boolean=} right if true, pad on the right side of the number rather than the left. 846 * Default is false. 847 */ 848 JSUtils.pad = function (str, length, right) { 849 if (typeof(str) !== 'string') { 850 str = "" + str; 851 } 852 var start = 0; 853 // take care of negative numbers 854 if (str.charAt(0) === '-') { 855 start++; 856 } 857 return (str.length >= length+start) ? str : 858 (right ? str + JSUtils.pad.zeros.substring(0,length-str.length+start) : 859 str.substring(0, start) + JSUtils.pad.zeros.substring(0,length-str.length+start) + str.substring(start)); 860 }; 861 862 /** @private */ 863 JSUtils.pad.zeros = "00000000000000000000000000000000"; 864 865 /** 866 * Convert a string into the hexadecimal representation 867 * of the Unicode characters in that string. 868 * 869 * @static 870 * @param {string} string The string to convert 871 * @param {number=} limit the number of digits to use to represent the character (1 to 8) 872 * @return {string} a hexadecimal representation of the 873 * Unicode characters in the input string 874 */ 875 JSUtils.toHexString = function(string, limit) { 876 var i, 877 result = "", 878 lim = (limit && limit < 9) ? limit : 4; 879 880 if (!string) { 881 return ""; 882 } 883 for (i = 0; i < string.length; i++) { 884 var ch = string.charCodeAt(i).toString(16); 885 result += JSUtils.pad(ch, lim); 886 } 887 return result.toUpperCase(); 888 }; 889 890 /** 891 * Test whether an object in a Javascript Date. 892 * 893 * @static 894 * @param {Object|null|undefined} object The object to test 895 * @return {boolean} return true if the object is a Date 896 * and false otherwise 897 */ 898 JSUtils.isDate = function(object) { 899 if (typeof(object) === 'object') { 900 return Object.prototype.toString.call(object) === '[object Date]'; 901 } 902 return false; 903 }; 904 905 /** 906 * Merge the properties of object2 into object1 in a deep manner and return a merged 907 * object. If the property exists in both objects, the value in object2 will overwrite 908 * the value in object1. If a property exists in object1, but not in object2, its value 909 * will not be touched. If a property exists in object2, but not in object1, it will be 910 * added to the merged result.<p> 911 * 912 * Name1 and name2 are for creating debug output only. They are not necessary.<p> 913 * 914 * 915 * @static 916 * @param {*} object1 the object to merge into 917 * @param {*} object2 the object to merge 918 * @param {boolean=} replace if true, replace the array elements in object1 with those in object2. 919 * If false, concatenate array elements in object1 with items in object2. 920 * @param {string=} name1 name of the object being merged into 921 * @param {string=} name2 name of the object being merged in 922 * @return {Object} the merged object 923 */ 924 JSUtils.merge = function (object1, object2, replace, name1, name2) { 925 var prop = undefined, 926 newObj = {}; 927 for (prop in object1) { 928 if (prop && typeof(object1[prop]) !== 'undefined') { 929 newObj[prop] = object1[prop]; 930 } 931 } 932 for (prop in object2) { 933 if (prop && typeof(object2[prop]) !== 'undefined') { 934 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 935 if (typeof(replace) !== 'boolean' || !replace) { 936 newObj[prop] = [].concat(object1[prop]); 937 newObj[prop] = newObj[prop].concat(object2[prop]); 938 } else { 939 newObj[prop] = object2[prop]; 940 } 941 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 942 newObj[prop] = JSUtils.merge(object1[prop], object2[prop], replace); 943 } else { 944 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 945 if (name1 && name2 && newObj[prop] == object2[prop]) { 946 console.log("Property " + prop + " in " + name1 + " is being overridden by the same value in " + name2); 947 } 948 newObj[prop] = object2[prop]; 949 } 950 } 951 } 952 return newObj; 953 }; 954 955 /** 956 * Return true if the given object has no properties.<p> 957 * 958 * 959 * @static 960 * @param {Object} obj the object to check 961 * @return {boolean} true if the given object has no properties, false otherwise 962 */ 963 JSUtils.isEmpty = function (obj) { 964 var prop = undefined; 965 966 if (!obj) { 967 return true; 968 } 969 970 for (prop in obj) { 971 if (prop && typeof(obj[prop]) !== 'undefined') { 972 return false; 973 } 974 } 975 return true; 976 }; 977 978 /** 979 * @static 980 */ 981 JSUtils.hashCode = function(obj) { 982 var hash = 0; 983 984 function addHash(hash, newValue) { 985 // co-prime numbers creates a nicely distributed hash 986 hash *= 65543; 987 hash += newValue; 988 hash %= 2147483647; 989 return hash; 990 } 991 992 function stringHash(str) { 993 var hash = 0; 994 for (var i = 0; i < str.length; i++) { 995 hash = addHash(hash, str.charCodeAt(i)); 996 } 997 return hash; 998 } 999 1000 switch (typeof(obj)) { 1001 case 'undefined': 1002 hash = 0; 1003 break; 1004 case 'string': 1005 hash = stringHash(obj); 1006 break; 1007 case 'function': 1008 case 'number': 1009 case 'xml': 1010 hash = stringHash(String(obj)); 1011 break; 1012 case 'boolean': 1013 hash = obj ? 1 : 0; 1014 break; 1015 case 'object': 1016 var props = []; 1017 for (var p in obj) { 1018 if (obj.hasOwnProperty(p)) { 1019 props.push(p); 1020 } 1021 } 1022 // make sure the order of the properties doesn't matter 1023 props.sort(); 1024 for (var i = 0; i < props.length; i++) { 1025 hash = addHash(hash, stringHash(props[i])); 1026 hash = addHash(hash, JSUtils.hashCode(obj[props[i]])); 1027 } 1028 break; 1029 } 1030 1031 return hash; 1032 }; 1033 1034 1035 1036 1037 /*< Locale.js */ 1038 /* 1039 * Locale.js - Locale specifier definition 1040 * 1041 * Copyright © 2012-2015, JEDLSoft 1042 * 1043 * Licensed under the Apache License, Version 2.0 (the "License"); 1044 * you may not use this file except in compliance with the License. 1045 * You may obtain a copy of the License at 1046 * 1047 * http://www.apache.org/licenses/LICENSE-2.0 1048 * 1049 * Unless required by applicable law or agreed to in writing, software 1050 * distributed under the License is distributed on an "AS IS" BASIS, 1051 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1052 * 1053 * See the License for the specific language governing permissions and 1054 * limitations under the License. 1055 */ 1056 1057 // !depends ilib.js JSUtils.js 1058 1059 1060 /** 1061 * @class 1062 * Create a new locale instance. Locales are specified either with a specifier string 1063 * that follows the BCP-47 convention (roughly: "language-region-script-variant") or 1064 * with 4 parameters that specify the language, region, variant, and script individually.<p> 1065 * 1066 * The language is given as an ISO 639-1 two-letter, lower-case language code. You 1067 * can find a full list of these codes at 1068 * <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes</a><p> 1069 * 1070 * The region is given as an ISO 3166-1 two-letter, upper-case region code. You can 1071 * find a full list of these codes at 1072 * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2</a>.<p> 1073 * 1074 * The variant is any string that does not contain a dash which further differentiates 1075 * locales from each other.<p> 1076 * 1077 * The script is given as the ISO 15924 four-letter script code. In some locales, 1078 * text may be validly written in more than one script. For example, Serbian is often 1079 * written in both Latin and Cyrillic, though not usually mixed together. You can find a 1080 * full list of these codes at 1081 * <a href="http://en.wikipedia.org/wiki/ISO_15924#List_of_codes">http://en.wikipedia.org/wiki/ISO_15924#List_of_codes</a>.<p> 1082 * 1083 * As an example in ilib, the script can be used in the date formatter. Dates formatted 1084 * in Serbian could have day-of-week names or month names written in the Latin 1085 * or Cyrillic script. Often one script is default such that sr-SR-Latn is the same 1086 * as sr-SR so the script code "Latn" can be left off of the locale spec.<p> 1087 * 1088 * Each part is optional, and an empty string in the specifier before or after a 1089 * dash or as a parameter to the constructor denotes an unspecified value. In this 1090 * case, many of the ilib functions will treat the locale as generic. For example 1091 * the locale "en-" is equivalent to "en" and to "en--" and denotes a locale 1092 * of "English" with an unspecified region and variant, which typically matches 1093 * any region or variant.<p> 1094 * 1095 * Without any arguments to the constructor, this function returns the locale of 1096 * the host Javascript engine.<p> 1097 * 1098 * 1099 * @constructor 1100 * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full 1101 * locale spec in BCP-47 format, or another Locale instance to copy from 1102 * @param {string=} region the ISO 3166 2-letter code for the region 1103 * @param {string=} variant the name of the variant of this locale, if any 1104 * @param {string=} script the ISO 15924 code of the script for this locale, if any 1105 */ 1106 var Locale = function(language, region, variant, script) { 1107 if (typeof(region) === 'undefined') { 1108 var spec = language || ilib.getLocale(); 1109 if (typeof(spec) === 'string') { 1110 var parts = spec.split('-'); 1111 for ( var i = 0; i < parts.length; i++ ) { 1112 if (Locale._isLanguageCode(parts[i])) { 1113 /** 1114 * @private 1115 * @type {string|undefined} 1116 */ 1117 this.language = parts[i]; 1118 } else if (Locale._isRegionCode(parts[i])) { 1119 /** 1120 * @private 1121 * @type {string|undefined} 1122 */ 1123 this.region = parts[i]; 1124 } else if (Locale._isScriptCode(parts[i])) { 1125 /** 1126 * @private 1127 * @type {string|undefined} 1128 */ 1129 this.script = parts[i]; 1130 } else { 1131 /** 1132 * @private 1133 * @type {string|undefined} 1134 */ 1135 this.variant = parts[i]; 1136 } 1137 } 1138 this.language = this.language || undefined; 1139 this.region = this.region || undefined; 1140 this.script = this.script || undefined; 1141 this.variant = this.variant || undefined; 1142 } else if (typeof(spec) === 'object') { 1143 this.language = spec.language || undefined; 1144 this.region = spec.region || undefined; 1145 this.script = spec.script || undefined; 1146 this.variant = spec.variant || undefined; 1147 } 1148 } else { 1149 if (language) { 1150 language = language.trim(); 1151 this.language = language.length > 0 ? language.toLowerCase() : undefined; 1152 } else { 1153 this.language = undefined; 1154 } 1155 if (region) { 1156 region = region.trim(); 1157 this.region = region.length > 0 ? region.toUpperCase() : undefined; 1158 } else { 1159 this.region = undefined; 1160 } 1161 if (variant) { 1162 variant = variant.trim(); 1163 this.variant = variant.length > 0 ? variant : undefined; 1164 } else { 1165 this.variant = undefined; 1166 } 1167 if (script) { 1168 script = script.trim(); 1169 this.script = script.length > 0 ? script : undefined; 1170 } else { 1171 this.script = undefined; 1172 } 1173 } 1174 this._genSpec(); 1175 }; 1176 1177 // from http://en.wikipedia.org/wiki/ISO_3166-1 1178 Locale.a2toa3regmap = { 1179 "AF": "AFG", 1180 "AX": "ALA", 1181 "AL": "ALB", 1182 "DZ": "DZA", 1183 "AS": "ASM", 1184 "AD": "AND", 1185 "AO": "AGO", 1186 "AI": "AIA", 1187 "AQ": "ATA", 1188 "AG": "ATG", 1189 "AR": "ARG", 1190 "AM": "ARM", 1191 "AW": "ABW", 1192 "AU": "AUS", 1193 "AT": "AUT", 1194 "AZ": "AZE", 1195 "BS": "BHS", 1196 "BH": "BHR", 1197 "BD": "BGD", 1198 "BB": "BRB", 1199 "BY": "BLR", 1200 "BE": "BEL", 1201 "BZ": "BLZ", 1202 "BJ": "BEN", 1203 "BM": "BMU", 1204 "BT": "BTN", 1205 "BO": "BOL", 1206 "BQ": "BES", 1207 "BA": "BIH", 1208 "BW": "BWA", 1209 "BV": "BVT", 1210 "BR": "BRA", 1211 "IO": "IOT", 1212 "BN": "BRN", 1213 "BG": "BGR", 1214 "BF": "BFA", 1215 "BI": "BDI", 1216 "KH": "KHM", 1217 "CM": "CMR", 1218 "CA": "CAN", 1219 "CV": "CPV", 1220 "KY": "CYM", 1221 "CF": "CAF", 1222 "TD": "TCD", 1223 "CL": "CHL", 1224 "CN": "CHN", 1225 "CX": "CXR", 1226 "CC": "CCK", 1227 "CO": "COL", 1228 "KM": "COM", 1229 "CG": "COG", 1230 "CD": "COD", 1231 "CK": "COK", 1232 "CR": "CRI", 1233 "CI": "CIV", 1234 "HR": "HRV", 1235 "CU": "CUB", 1236 "CW": "CUW", 1237 "CY": "CYP", 1238 "CZ": "CZE", 1239 "DK": "DNK", 1240 "DJ": "DJI", 1241 "DM": "DMA", 1242 "DO": "DOM", 1243 "EC": "ECU", 1244 "EG": "EGY", 1245 "SV": "SLV", 1246 "GQ": "GNQ", 1247 "ER": "ERI", 1248 "EE": "EST", 1249 "ET": "ETH", 1250 "FK": "FLK", 1251 "FO": "FRO", 1252 "FJ": "FJI", 1253 "FI": "FIN", 1254 "FR": "FRA", 1255 "GF": "GUF", 1256 "PF": "PYF", 1257 "TF": "ATF", 1258 "GA": "GAB", 1259 "GM": "GMB", 1260 "GE": "GEO", 1261 "DE": "DEU", 1262 "GH": "GHA", 1263 "GI": "GIB", 1264 "GR": "GRC", 1265 "GL": "GRL", 1266 "GD": "GRD", 1267 "GP": "GLP", 1268 "GU": "GUM", 1269 "GT": "GTM", 1270 "GG": "GGY", 1271 "GN": "GIN", 1272 "GW": "GNB", 1273 "GY": "GUY", 1274 "HT": "HTI", 1275 "HM": "HMD", 1276 "VA": "VAT", 1277 "HN": "HND", 1278 "HK": "HKG", 1279 "HU": "HUN", 1280 "IS": "ISL", 1281 "IN": "IND", 1282 "ID": "IDN", 1283 "IR": "IRN", 1284 "IQ": "IRQ", 1285 "IE": "IRL", 1286 "IM": "IMN", 1287 "IL": "ISR", 1288 "IT": "ITA", 1289 "JM": "JAM", 1290 "JP": "JPN", 1291 "JE": "JEY", 1292 "JO": "JOR", 1293 "KZ": "KAZ", 1294 "KE": "KEN", 1295 "KI": "KIR", 1296 "KP": "PRK", 1297 "KR": "KOR", 1298 "KW": "KWT", 1299 "KG": "KGZ", 1300 "LA": "LAO", 1301 "LV": "LVA", 1302 "LB": "LBN", 1303 "LS": "LSO", 1304 "LR": "LBR", 1305 "LY": "LBY", 1306 "LI": "LIE", 1307 "LT": "LTU", 1308 "LU": "LUX", 1309 "MO": "MAC", 1310 "MK": "MKD", 1311 "MG": "MDG", 1312 "MW": "MWI", 1313 "MY": "MYS", 1314 "MV": "MDV", 1315 "ML": "MLI", 1316 "MT": "MLT", 1317 "MH": "MHL", 1318 "MQ": "MTQ", 1319 "MR": "MRT", 1320 "MU": "MUS", 1321 "YT": "MYT", 1322 "MX": "MEX", 1323 "FM": "FSM", 1324 "MD": "MDA", 1325 "MC": "MCO", 1326 "MN": "MNG", 1327 "ME": "MNE", 1328 "MS": "MSR", 1329 "MA": "MAR", 1330 "MZ": "MOZ", 1331 "MM": "MMR", 1332 "NA": "NAM", 1333 "NR": "NRU", 1334 "NP": "NPL", 1335 "NL": "NLD", 1336 "NC": "NCL", 1337 "NZ": "NZL", 1338 "NI": "NIC", 1339 "NE": "NER", 1340 "NG": "NGA", 1341 "NU": "NIU", 1342 "NF": "NFK", 1343 "MP": "MNP", 1344 "NO": "NOR", 1345 "OM": "OMN", 1346 "PK": "PAK", 1347 "PW": "PLW", 1348 "PS": "PSE", 1349 "PA": "PAN", 1350 "PG": "PNG", 1351 "PY": "PRY", 1352 "PE": "PER", 1353 "PH": "PHL", 1354 "PN": "PCN", 1355 "PL": "POL", 1356 "PT": "PRT", 1357 "PR": "PRI", 1358 "QA": "QAT", 1359 "RE": "REU", 1360 "RO": "ROU", 1361 "RU": "RUS", 1362 "RW": "RWA", 1363 "BL": "BLM", 1364 "SH": "SHN", 1365 "KN": "KNA", 1366 "LC": "LCA", 1367 "MF": "MAF", 1368 "PM": "SPM", 1369 "VC": "VCT", 1370 "WS": "WSM", 1371 "SM": "SMR", 1372 "ST": "STP", 1373 "SA": "SAU", 1374 "SN": "SEN", 1375 "RS": "SRB", 1376 "SC": "SYC", 1377 "SL": "SLE", 1378 "SG": "SGP", 1379 "SX": "SXM", 1380 "SK": "SVK", 1381 "SI": "SVN", 1382 "SB": "SLB", 1383 "SO": "SOM", 1384 "ZA": "ZAF", 1385 "GS": "SGS", 1386 "SS": "SSD", 1387 "ES": "ESP", 1388 "LK": "LKA", 1389 "SD": "SDN", 1390 "SR": "SUR", 1391 "SJ": "SJM", 1392 "SZ": "SWZ", 1393 "SE": "SWE", 1394 "CH": "CHE", 1395 "SY": "SYR", 1396 "TW": "TWN", 1397 "TJ": "TJK", 1398 "TZ": "TZA", 1399 "TH": "THA", 1400 "TL": "TLS", 1401 "TG": "TGO", 1402 "TK": "TKL", 1403 "TO": "TON", 1404 "TT": "TTO", 1405 "TN": "TUN", 1406 "TR": "TUR", 1407 "TM": "TKM", 1408 "TC": "TCA", 1409 "TV": "TUV", 1410 "UG": "UGA", 1411 "UA": "UKR", 1412 "AE": "ARE", 1413 "GB": "GBR", 1414 "US": "USA", 1415 "UM": "UMI", 1416 "UY": "URY", 1417 "UZ": "UZB", 1418 "VU": "VUT", 1419 "VE": "VEN", 1420 "VN": "VNM", 1421 "VG": "VGB", 1422 "VI": "VIR", 1423 "WF": "WLF", 1424 "EH": "ESH", 1425 "YE": "YEM", 1426 "ZM": "ZMB", 1427 "ZW": "ZWE" 1428 }; 1429 1430 1431 Locale.a1toa3langmap = { 1432 "ab": "abk", 1433 "aa": "aar", 1434 "af": "afr", 1435 "ak": "aka", 1436 "sq": "sqi", 1437 "am": "amh", 1438 "ar": "ara", 1439 "an": "arg", 1440 "hy": "hye", 1441 "as": "asm", 1442 "av": "ava", 1443 "ae": "ave", 1444 "ay": "aym", 1445 "az": "aze", 1446 "bm": "bam", 1447 "ba": "bak", 1448 "eu": "eus", 1449 "be": "bel", 1450 "bn": "ben", 1451 "bh": "bih", 1452 "bi": "bis", 1453 "bs": "bos", 1454 "br": "bre", 1455 "bg": "bul", 1456 "my": "mya", 1457 "ca": "cat", 1458 "ch": "cha", 1459 "ce": "che", 1460 "ny": "nya", 1461 "zh": "zho", 1462 "cv": "chv", 1463 "kw": "cor", 1464 "co": "cos", 1465 "cr": "cre", 1466 "hr": "hrv", 1467 "cs": "ces", 1468 "da": "dan", 1469 "dv": "div", 1470 "nl": "nld", 1471 "dz": "dzo", 1472 "en": "eng", 1473 "eo": "epo", 1474 "et": "est", 1475 "ee": "ewe", 1476 "fo": "fao", 1477 "fj": "fij", 1478 "fi": "fin", 1479 "fr": "fra", 1480 "ff": "ful", 1481 "gl": "glg", 1482 "ka": "kat", 1483 "de": "deu", 1484 "el": "ell", 1485 "gn": "grn", 1486 "gu": "guj", 1487 "ht": "hat", 1488 "ha": "hau", 1489 "he": "heb", 1490 "hz": "her", 1491 "hi": "hin", 1492 "ho": "hmo", 1493 "hu": "hun", 1494 "ia": "ina", 1495 "id": "ind", 1496 "ie": "ile", 1497 "ga": "gle", 1498 "ig": "ibo", 1499 "ik": "ipk", 1500 "io": "ido", 1501 "is": "isl", 1502 "it": "ita", 1503 "iu": "iku", 1504 "ja": "jpn", 1505 "jv": "jav", 1506 "kl": "kal", 1507 "kn": "kan", 1508 "kr": "kau", 1509 "ks": "kas", 1510 "kk": "kaz", 1511 "km": "khm", 1512 "ki": "kik", 1513 "rw": "kin", 1514 "ky": "kir", 1515 "kv": "kom", 1516 "kg": "kon", 1517 "ko": "kor", 1518 "ku": "kur", 1519 "kj": "kua", 1520 "la": "lat", 1521 "lb": "ltz", 1522 "lg": "lug", 1523 "li": "lim", 1524 "ln": "lin", 1525 "lo": "lao", 1526 "lt": "lit", 1527 "lu": "lub", 1528 "lv": "lav", 1529 "gv": "glv", 1530 "mk": "mkd", 1531 "mg": "mlg", 1532 "ms": "msa", 1533 "ml": "mal", 1534 "mt": "mlt", 1535 "mi": "mri", 1536 "mr": "mar", 1537 "mh": "mah", 1538 "mn": "mon", 1539 "na": "nau", 1540 "nv": "nav", 1541 "nb": "nob", 1542 "nd": "nde", 1543 "ne": "nep", 1544 "ng": "ndo", 1545 "nn": "nno", 1546 "no": "nor", 1547 "ii": "iii", 1548 "nr": "nbl", 1549 "oc": "oci", 1550 "oj": "oji", 1551 "cu": "chu", 1552 "om": "orm", 1553 "or": "ori", 1554 "os": "oss", 1555 "pa": "pan", 1556 "pi": "pli", 1557 "fa": "fas", 1558 "pl": "pol", 1559 "ps": "pus", 1560 "pt": "por", 1561 "qu": "que", 1562 "rm": "roh", 1563 "rn": "run", 1564 "ro": "ron", 1565 "ru": "rus", 1566 "sa": "san", 1567 "sc": "srd", 1568 "sd": "snd", 1569 "se": "sme", 1570 "sm": "smo", 1571 "sg": "sag", 1572 "sr": "srp", 1573 "gd": "gla", 1574 "sn": "sna", 1575 "si": "sin", 1576 "sk": "slk", 1577 "sl": "slv", 1578 "so": "som", 1579 "st": "sot", 1580 "es": "spa", 1581 "su": "sun", 1582 "sw": "swa", 1583 "ss": "ssw", 1584 "sv": "swe", 1585 "ta": "tam", 1586 "te": "tel", 1587 "tg": "tgk", 1588 "th": "tha", 1589 "ti": "tir", 1590 "bo": "bod", 1591 "tk": "tuk", 1592 "tl": "tgl", 1593 "tn": "tsn", 1594 "to": "ton", 1595 "tr": "tur", 1596 "ts": "tso", 1597 "tt": "tat", 1598 "tw": "twi", 1599 "ty": "tah", 1600 "ug": "uig", 1601 "uk": "ukr", 1602 "ur": "urd", 1603 "uz": "uzb", 1604 "ve": "ven", 1605 "vi": "vie", 1606 "vo": "vol", 1607 "wa": "wln", 1608 "cy": "cym", 1609 "wo": "wol", 1610 "fy": "fry", 1611 "xh": "xho", 1612 "yi": "yid", 1613 "yo": "yor", 1614 "za": "zha", 1615 "zu": "zul" 1616 }; 1617 1618 /** 1619 * Tell whether or not the str does not start with a lower case ASCII char. 1620 * @private 1621 * @param {string} str the char to check 1622 * @return {boolean} true if the char is not a lower case ASCII char 1623 */ 1624 Locale._notLower = function(str) { 1625 // do this with ASCII only so we don't have to depend on the CType functions 1626 var ch = str.charCodeAt(0); 1627 return ch < 97 || ch > 122; 1628 }; 1629 1630 /** 1631 * Tell whether or not the str does not start with an upper case ASCII char. 1632 * @private 1633 * @param {string} str the char to check 1634 * @return {boolean} true if the char is a not an upper case ASCII char 1635 */ 1636 Locale._notUpper = function(str) { 1637 // do this with ASCII only so we don't have to depend on the CType functions 1638 var ch = str.charCodeAt(0); 1639 return ch < 65 || ch > 90; 1640 }; 1641 1642 /** 1643 * Tell whether or not the str does not start with a digit char. 1644 * @private 1645 * @param {string} str the char to check 1646 * @return {boolean} true if the char is a not an upper case ASCII char 1647 */ 1648 Locale._notDigit = function(str) { 1649 // do this with ASCII only so we don't have to depend on the CType functions 1650 var ch = str.charCodeAt(0); 1651 return ch < 48 || ch > 57; 1652 }; 1653 1654 /** 1655 * Tell whether or not the given string has the correct syntax to be 1656 * an ISO 639 language code. 1657 * 1658 * @private 1659 * @param {string} str the string to parse 1660 * @return {boolean} true if the string could syntactically be a language code. 1661 */ 1662 Locale._isLanguageCode = function(str) { 1663 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1664 return false; 1665 } 1666 1667 for (var i = 0; i < str.length; i++) { 1668 if (Locale._notLower(str.charAt(i))) { 1669 return false; 1670 } 1671 } 1672 1673 return true; 1674 }; 1675 1676 /** 1677 * Tell whether or not the given string has the correct syntax to be 1678 * an ISO 3166 2-letter region code or M.49 3-digit region code. 1679 * 1680 * @private 1681 * @param {string} str the string to parse 1682 * @return {boolean} true if the string could syntactically be a language code. 1683 */ 1684 Locale._isRegionCode = function (str) { 1685 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1686 return false; 1687 } 1688 1689 if (str.length === 2) { 1690 for (var i = 0; i < str.length; i++) { 1691 if (Locale._notUpper(str.charAt(i))) { 1692 return false; 1693 } 1694 } 1695 } else { 1696 for (var i = 0; i < str.length; i++) { 1697 if (Locale._notDigit(str.charAt(i))) { 1698 return false; 1699 } 1700 } 1701 } 1702 1703 return true; 1704 }; 1705 1706 /** 1707 * Tell whether or not the given string has the correct syntax to be 1708 * an ISO 639 language code. 1709 * 1710 * @private 1711 * @param {string} str the string to parse 1712 * @return {boolean} true if the string could syntactically be a language code. 1713 */ 1714 Locale._isScriptCode = function(str) { 1715 if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) { 1716 return false; 1717 } 1718 1719 for (var i = 1; i < 4; i++) { 1720 if (Locale._notLower(str.charAt(i))) { 1721 return false; 1722 } 1723 } 1724 1725 return true; 1726 }; 1727 1728 /** 1729 * Return the ISO-3166 alpha3 equivalent region code for the given ISO 3166 alpha2 1730 * region code. If the given alpha2 code is not found, this function returns its 1731 * argument unchanged. 1732 * @static 1733 * @param {string|undefined} alpha2 the alpha2 code to map 1734 * @return {string|undefined} the alpha3 equivalent of the given alpha2 code, or the alpha2 1735 * parameter if the alpha2 value is not found 1736 */ 1737 Locale.regionAlpha2ToAlpha3 = function(alpha2) { 1738 return Locale.a2toa3regmap[alpha2] || alpha2; 1739 }; 1740 1741 /** 1742 * Return the ISO-639 alpha3 equivalent language code for the given ISO 639 alpha1 1743 * language code. If the given alpha1 code is not found, this function returns its 1744 * argument unchanged. 1745 * @static 1746 * @param {string|undefined} alpha1 the alpha1 code to map 1747 * @return {string|undefined} the alpha3 equivalent of the given alpha1 code, or the alpha1 1748 * parameter if the alpha1 value is not found 1749 */ 1750 Locale.languageAlpha1ToAlpha3 = function(alpha1) { 1751 return Locale.a1toa3langmap[alpha1] || alpha1; 1752 }; 1753 1754 Locale.prototype = { 1755 /** 1756 * @private 1757 */ 1758 _genSpec: function () { 1759 this.spec = this.language || ""; 1760 1761 if (this.script) { 1762 if (this.spec.length > 0) { 1763 this.spec += "-"; 1764 } 1765 this.spec += this.script; 1766 } 1767 1768 if (this.region) { 1769 if (this.spec.length > 0) { 1770 this.spec += "-"; 1771 } 1772 this.spec += this.region; 1773 } 1774 1775 if (this.variant) { 1776 if (this.spec.length > 0) { 1777 this.spec += "-"; 1778 } 1779 this.spec += this.variant; 1780 } 1781 }, 1782 1783 /** 1784 * Return the ISO 639 language code for this locale. 1785 * @return {string|undefined} the language code for this locale 1786 */ 1787 getLanguage: function() { 1788 return this.language; 1789 }, 1790 1791 /** 1792 * Return the language of this locale as an ISO-639-alpha3 language code 1793 * @return {string|undefined} the alpha3 language code of this locale 1794 */ 1795 getLanguageAlpha3: function() { 1796 return Locale.languageAlpha1ToAlpha3(this.language); 1797 }, 1798 1799 /** 1800 * Return the ISO 3166 region code for this locale. 1801 * @return {string|undefined} the region code of this locale 1802 */ 1803 getRegion: function() { 1804 return this.region; 1805 }, 1806 1807 /** 1808 * Return the region of this locale as an ISO-3166-alpha3 region code 1809 * @return {string|undefined} the alpha3 region code of this locale 1810 */ 1811 getRegionAlpha3: function() { 1812 return Locale.regionAlpha2ToAlpha3(this.region); 1813 }, 1814 1815 /** 1816 * Return the ISO 15924 script code for this locale 1817 * @return {string|undefined} the script code of this locale 1818 */ 1819 getScript: function () { 1820 return this.script; 1821 }, 1822 1823 /** 1824 * Return the variant code for this locale 1825 * @return {string|undefined} the variant code of this locale, if any 1826 */ 1827 getVariant: function() { 1828 return this.variant; 1829 }, 1830 1831 /** 1832 * Return the whole locale specifier as a string. 1833 * @return {string} the locale specifier 1834 */ 1835 getSpec: function() { 1836 return this.spec; 1837 }, 1838 1839 /** 1840 * Express this locale object as a string. Currently, this simply calls the getSpec 1841 * function to represent the locale as its specifier. 1842 * 1843 * @return {string} the locale specifier 1844 */ 1845 toString: function() { 1846 return this.getSpec(); 1847 }, 1848 1849 /** 1850 * Return true if the the other locale is exactly equal to the current one. 1851 * @return {boolean} whether or not the other locale is equal to the current one 1852 */ 1853 equals: function(other) { 1854 return this.language === other.language && 1855 this.region === other.region && 1856 this.script === other.script && 1857 this.variant === other.variant; 1858 }, 1859 1860 /** 1861 * Return true if the current locale is the special pseudo locale. 1862 * @return {boolean} true if the current locale is the special pseudo locale 1863 */ 1864 isPseudo: function () { 1865 return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1; 1866 } 1867 }; 1868 1869 // static functions 1870 /** 1871 * @private 1872 */ 1873 Locale.locales = [ 1874 1875 ]; 1876 1877 /** 1878 * Return the list of available locales that this iLib file supports. 1879 * If this copy of ilib is pre-assembled with locale data, then the 1880 * list locales may be much smaller 1881 * than the list of all available locales in the iLib repository. The 1882 * assembly tool will automatically fill in the list for an assembled 1883 * copy of iLib. If this copy is being used with dynamically loaded 1884 * data, then you 1885 * can load any locale that iLib supports. You can form a locale with any 1886 * combination of a language and region tags that exist in the locale 1887 * data directory. Language tags are in the root of the locale data dir, 1888 * and region tags can be found underneath the "und" directory. (The 1889 * region tags are separated into a different dir because the region names 1890 * conflict with language names on file systems that are case-insensitive.) 1891 * If you have culled the locale data directory to limit the size of 1892 * your app, then this function should return only those files that actually exist 1893 * according to the ilibmanifest.json file in the root of that locale 1894 * data dir. Make sure your ilibmanifest.json file is up-to-date with 1895 * respect to the list of files that exist in the locale data dir. 1896 * 1897 * @param {boolean} sync if false, load the list of available files from disk 1898 * asynchronously, otherwise load them synchronously. (Default: true/synchronously) 1899 * @param {Function} onLoad a callback function to call if asynchronous 1900 * load was requested and the list of files have been loaded. 1901 * @return {Array.<string>} this is an array of locale specs for which 1902 * this iLib file has locale data for 1903 */ 1904 Locale.getAvailableLocales = function (sync, onLoad) { 1905 var locales = []; 1906 if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') { 1907 locales = Locale.locales; 1908 if (onLoad && typeof(onLoad) === 'function') { 1909 onLoad(locales); 1910 } 1911 } else { 1912 if (typeof(sync) === 'undefined') { 1913 sync = true; 1914 } 1915 ilib._load.listAvailableFiles(sync, function(manifest) { 1916 if (manifest) { 1917 for (var dir in manifest) { 1918 var filelist = manifest[dir]; 1919 for (var i = 0; i < filelist.length; i++) { 1920 if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") { 1921 locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-")); 1922 } 1923 } 1924 } 1925 } 1926 if (onLoad && typeof(onLoad) === 'function') { 1927 onLoad(locales); 1928 } 1929 }); 1930 } 1931 return locales; 1932 }; 1933 1934 1935 1936 /*< Utils.js */ 1937 /* 1938 * Utils.js - Core utility routines 1939 * 1940 * Copyright © 2012-2015, JEDLSoft 1941 * 1942 * Licensed under the Apache License, Version 2.0 (the "License"); 1943 * you may not use this file except in compliance with the License. 1944 * You may obtain a copy of the License at 1945 * 1946 * http://www.apache.org/licenses/LICENSE-2.0 1947 * 1948 * Unless required by applicable law or agreed to in writing, software 1949 * distributed under the License is distributed on an "AS IS" BASIS, 1950 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1951 * 1952 * See the License for the specific language governing permissions and 1953 * limitations under the License. 1954 */ 1955 1956 // !depends ilib.js Locale.js JSUtils.js 1957 1958 1959 var Utils = {}; 1960 1961 /** 1962 * Find and merge all the locale data for a particular prefix in the given locale 1963 * and return it as a single javascript object. This merges the data in the 1964 * correct order: 1965 * 1966 * <ol> 1967 * <li>shared data (usually English) 1968 * <li>data for language 1969 * <li>data for language + region 1970 * <li>data for language + region + script 1971 * <li>data for language + region + script + variant 1972 * </ol> 1973 * 1974 * It is okay for any of the above to be missing. This function will just skip the 1975 * missing data. However, if everything except the shared data is missing, this 1976 * function returns undefined, allowing the caller to go and dynamically load the 1977 * data instead. 1978 * 1979 * @static 1980 * @param {string} prefix prefix under ilib.data of the data to merge 1981 * @param {Locale} locale locale of the data being sought 1982 * @param {boolean=} replaceArrays if true, replace the array elements in object1 with those in object2. 1983 * If false, concatenate array elements in object1 with items in object2. 1984 * @param {boolean=} returnOne if true, only return the most locale-specific data. If false, 1985 * merge all the relevant locale data together. 1986 * @return {Object?} the merged locale data 1987 */ 1988 Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) { 1989 var data = undefined; 1990 var loc = locale || new Locale(); 1991 var foundLocaleData = false; 1992 var property = prefix; 1993 var mostSpecific; 1994 1995 data = ilib.data[prefix] || {}; 1996 1997 mostSpecific = data; 1998 1999 if (loc.getLanguage()) { 2000 property = prefix + '_' + loc.getLanguage(); 2001 if (ilib.data[property]) { 2002 foundLocaleData = true; 2003 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2004 mostSpecific = ilib.data[property]; 2005 } 2006 } 2007 2008 if (loc.getRegion()) { 2009 property = prefix + '_' + loc.getRegion(); 2010 if (ilib.data[property]) { 2011 foundLocaleData = true; 2012 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2013 mostSpecific = ilib.data[property]; 2014 } 2015 } 2016 2017 if (loc.getLanguage()) { 2018 property = prefix + '_' + loc.getLanguage(); 2019 2020 if (loc.getScript()) { 2021 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript(); 2022 if (ilib.data[property]) { 2023 foundLocaleData = true; 2024 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2025 mostSpecific = ilib.data[property]; 2026 } 2027 } 2028 2029 if (loc.getRegion()) { 2030 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion(); 2031 if (ilib.data[property]) { 2032 foundLocaleData = true; 2033 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2034 mostSpecific = ilib.data[property]; 2035 } 2036 } 2037 } 2038 2039 if (loc.getRegion() && loc.getVariant()) { 2040 property = prefix + '_' + loc.getLanguage() + '_' + loc.getVariant(); 2041 if (ilib.data[property]) { 2042 foundLocaleData = true; 2043 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2044 mostSpecific = ilib.data[property]; 2045 } 2046 } 2047 2048 if (loc.getLanguage() && loc.getScript() && loc.getRegion()) { 2049 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion(); 2050 if (ilib.data[property]) { 2051 foundLocaleData = true; 2052 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2053 mostSpecific = ilib.data[property]; 2054 } 2055 } 2056 2057 if (loc.getLanguage() && loc.getRegion() && loc.getVariant()) { 2058 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2059 if (ilib.data[property]) { 2060 foundLocaleData = true; 2061 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2062 mostSpecific = ilib.data[property]; 2063 } 2064 } 2065 2066 if (loc.getLanguage() && loc.getScript() && loc.getRegion() && loc.getVariant()) { 2067 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2068 if (ilib.data[property]) { 2069 foundLocaleData = true; 2070 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2071 mostSpecific = ilib.data[property]; 2072 } 2073 } 2074 2075 return foundLocaleData ? (returnOne ? mostSpecific : data) : undefined; 2076 }; 2077 2078 /** 2079 * Return an array of relative path names for the 2080 * files that represent the data for the given locale.<p> 2081 * 2082 * Note that to prevent the situation where a directory for 2083 * a language exists next to the directory for a region where 2084 * the language code and region code differ only by case, the 2085 * plain region directories are located under the special 2086 * "undefined" language directory which has the ISO code "und". 2087 * The reason is that some platforms have case-insensitive 2088 * file systems, and you cannot have 2 directories with the 2089 * same name which only differ by case. For example, "es" is 2090 * the ISO 639 code for the language "Spanish" and "ES" is 2091 * the ISO 3166 code for the region "Spain", so both the 2092 * directories cannot exist underneath "locale". The region 2093 * therefore will be loaded from "und/ES" instead.<p> 2094 * 2095 * <h4>Variations</h4> 2096 * 2097 * With only language and region specified, the following 2098 * sequence of paths will be generated:<p> 2099 * 2100 * <pre> 2101 * language 2102 * und/region 2103 * language/region 2104 * </pre> 2105 * 2106 * With only language and script specified:<p> 2107 * 2108 * <pre> 2109 * language 2110 * language/script 2111 * </pre> 2112 * 2113 * With only script and region specified:<p> 2114 * 2115 * <pre> 2116 * und/region 2117 * </pre> 2118 * 2119 * With only region and variant specified:<p> 2120 * 2121 * <pre> 2122 * und/region 2123 * region/variant 2124 * </pre> 2125 * 2126 * With only language, script, and region specified:<p> 2127 * 2128 * <pre> 2129 * language 2130 * und/region 2131 * language/script 2132 * language/region 2133 * language/script/region 2134 * </pre> 2135 * 2136 * With only language, region, and variant specified:<p> 2137 * 2138 * <pre> 2139 * language 2140 * und/region 2141 * language/region 2142 * region/variant 2143 * language/region/variant 2144 * </pre> 2145 * 2146 * With all parts specified:<p> 2147 * 2148 * <pre> 2149 * language 2150 * und/region 2151 * language/script 2152 * language/region 2153 * region/variant 2154 * language/script/region 2155 * language/region/variant 2156 * language/script/region/variant 2157 * </pre> 2158 * 2159 * @static 2160 * @param {Locale} locale load the files for this locale 2161 * @param {string?} name the file name of each file to load without 2162 * any path 2163 * @return {Array.<string>} An array of relative path names 2164 * for the files that contain the locale data 2165 */ 2166 Utils.getLocFiles = function(locale, name) { 2167 var dir = ""; 2168 var files = []; 2169 var filename = name || "resources.json"; 2170 var loc = locale || new Locale(); 2171 2172 var language = loc.getLanguage(); 2173 var region = loc.getRegion(); 2174 var script = loc.getScript(); 2175 var variant = loc.getVariant(); 2176 2177 files.push(filename); // generic shared file 2178 2179 if (language) { 2180 dir = language + "/"; 2181 files.push(dir + filename); 2182 } 2183 2184 if (region) { 2185 dir = "und/" + region + "/"; 2186 files.push(dir + filename); 2187 } 2188 2189 if (language) { 2190 if (script) { 2191 dir = language + "/" + script + "/"; 2192 files.push(dir + filename); 2193 } 2194 if (region) { 2195 dir = language + "/" + region + "/"; 2196 files.push(dir + filename); 2197 } 2198 } 2199 2200 if (region && variant) { 2201 dir = "und/" + region + "/" + variant + "/"; 2202 files.push(dir + filename); 2203 } 2204 2205 if (language && script && region) { 2206 dir = language + "/" + script + "/" + region + "/"; 2207 files.push(dir + filename); 2208 } 2209 2210 if (language && region && variant) { 2211 dir = language + "/" + region + "/" + variant + "/"; 2212 files.push(dir + filename); 2213 } 2214 2215 if (language && script && region && variant) { 2216 dir = language + "/" + script + "/" + region + "/" + variant + "/"; 2217 files.push(dir + filename); 2218 } 2219 2220 return files; 2221 }; 2222 2223 /** 2224 * Load data using the new loader object or via the old function callback. 2225 * @static 2226 * @private 2227 */ 2228 Utils._callLoadData = function (files, sync, params, callback) { 2229 // console.log("Utils._callLoadData called"); 2230 if (typeof(ilib._load) === 'function') { 2231 // console.log("Utils._callLoadData: calling as a regular function"); 2232 return ilib._load(files, sync, params, callback); 2233 } else if (typeof(ilib._load) === 'object' && typeof(ilib._load.loadFiles) === 'function') { 2234 // console.log("Utils._callLoadData: calling as an object"); 2235 return ilib._load.loadFiles(files, sync, params, callback); 2236 } 2237 2238 // console.log("Utils._callLoadData: not calling. Type is " + typeof(ilib._load) + " and instanceof says " + (ilib._load instanceof Loader)); 2239 return undefined; 2240 }; 2241 2242 /** 2243 * Find locale data or load it in. If the data with the given name is preassembled, it will 2244 * find the data in ilib.data. If the data is not preassembled but there is a loader function, 2245 * this function will call it to load the data. Otherwise, the callback will be called with 2246 * undefined as the data. This function will create a cache under the given class object. 2247 * If data was successfully loaded, it will be set into the cache so that future access to 2248 * the same data for the same locale is much quicker.<p> 2249 * 2250 * The parameters can specify any of the following properties:<p> 2251 * 2252 * <ul> 2253 * <li><i>name</i> - String. The name of the file being loaded. Default: ResBundle.json 2254 * <li><i>object</i> - Object. The class attempting to load data. The cache is stored inside of here. 2255 * <li><i>locale</i> - Locale. The locale for which data is loaded. Default is the current locale. 2256 * <li><i>nonlocale</i> - boolean. If true, the data being loaded is not locale-specific. 2257 * <li><i>type</i> - String. Type of file to load. This can be "json" or "other" type. Default: "json" 2258 * <li><i>replace</i> - boolean. When merging json objects, this parameter controls whether to merge arrays 2259 * or have arrays replace each other. If true, arrays in child objects replace the arrays in parent 2260 * objects. When false, the arrays in child objects are concatenated with the arrays in parent objects. 2261 * <li><i>loadParams</i> - Object. An object with parameters to pass to the loader function 2262 * <li><i>sync</i> - boolean. Whether or not to load the data synchronously 2263 * <li><i>callback</i> - function(?)=. callback Call back function to call when the data is available. 2264 * Data is not returned from this method, so a callback function is mandatory. 2265 * </ul> 2266 * 2267 * @static 2268 * @param {Object} params Parameters configuring how to load the files (see above) 2269 */ 2270 Utils.loadData = function(params) { 2271 var name = "resources.json", 2272 object = undefined, 2273 locale = new Locale(ilib.getLocale()), 2274 sync = false, 2275 type = undefined, 2276 loadParams = {}, 2277 callback = undefined, 2278 nonlocale = false, 2279 replace = false, 2280 basename; 2281 2282 if (!params || typeof(params.callback) !== 'function') { 2283 return; 2284 } 2285 2286 if (params.name) { 2287 name = params.name; 2288 } 2289 if (params.object) { 2290 object = params.object; 2291 } 2292 if (params.locale) { 2293 locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 2294 } 2295 if (params.type) { 2296 type = params.type; 2297 } 2298 if (params.loadParams) { 2299 loadParams = params.loadParams; 2300 } 2301 if (params.sync) { 2302 sync = params.sync; 2303 } 2304 if (params.nonlocale) { 2305 nonlocale = !!params.nonlocale; 2306 } 2307 if (typeof(params.replace) === 'boolean') { 2308 replace = params.replace; 2309 } 2310 2311 callback = params.callback; 2312 2313 if (object && !object.cache) { 2314 object.cache = {}; 2315 } 2316 2317 if (!type) { 2318 var dot = name.lastIndexOf("."); 2319 type = (dot !== -1) ? name.substring(dot+1) : "text"; 2320 } 2321 2322 var spec = ((!nonlocale && locale.getSpec().replace(/-/g, '_')) || "root") + "," + name + "," + String(JSUtils.hashCode(loadParams)); 2323 if (!object || typeof(object.cache[spec]) === 'undefined') { 2324 var data, returnOne = (loadParams && loadParams.returnOne); 2325 2326 if (type === "json") { 2327 // console.log("type is json"); 2328 basename = name.substring(0, name.lastIndexOf(".")); 2329 if (nonlocale) { 2330 basename = basename.replace(/[\.:\(\)\/\\\+\-]/g, "_"); 2331 data = ilib.data[basename]; 2332 } else { 2333 data = Utils.mergeLocData(basename, locale, replace, returnOne); 2334 } 2335 if (data) { 2336 // console.log("found assembled data"); 2337 if (object) { 2338 object.cache[spec] = data; 2339 } 2340 callback(data); 2341 return; 2342 } 2343 } 2344 2345 // console.log("ilib._load is " + typeof(ilib._load)); 2346 if (typeof(ilib._load) !== 'undefined') { 2347 // the data is not preassembled, so attempt to load it dynamically 2348 var files = nonlocale ? [ name || "resources.json" ] : Utils.getLocFiles(locale, name); 2349 if (type !== "json") { 2350 loadParams.returnOne = true; 2351 } 2352 2353 Utils._callLoadData(files, sync, loadParams, ilib.bind(this, function(arr) { 2354 if (type === "json") { 2355 data = ilib.data[basename] || {}; 2356 for (var i = 0; i < arr.length; i++) { 2357 if (typeof(arr[i]) !== 'undefined') { 2358 data = loadParams.returnOne ? arr[i] : JSUtils.merge(data, arr[i], replace); 2359 } 2360 } 2361 2362 if (object) { 2363 object.cache[spec] = data; 2364 } 2365 callback(data); 2366 } else { 2367 var i = arr.length-1; 2368 while (i > -1 && !arr[i]) { 2369 i--; 2370 } 2371 if (i > -1) { 2372 if (object) { 2373 object.cache[spec] = arr[i]; 2374 } 2375 callback(arr[i]); 2376 } else { 2377 callback(undefined); 2378 } 2379 } 2380 })); 2381 } else { 2382 // no data other than the generic shared data 2383 if (type === "json") { 2384 data = ilib.data[basename]; 2385 } 2386 if (object && data) { 2387 object.cache[spec] = data; 2388 } 2389 callback(data); 2390 } 2391 } else { 2392 callback(object.cache[spec]); 2393 } 2394 }; 2395 2396 2397 /*< LocaleInfo.js */ 2398 /* 2399 * LocaleInfo.js - Encode locale-specific defaults 2400 * 2401 * Copyright © 2012-2015, JEDLSoft 2402 * 2403 * Licensed under the Apache License, Version 2.0 (the "License"); 2404 * you may not use this file except in compliance with the License. 2405 * You may obtain a copy of the License at 2406 * 2407 * http://www.apache.org/licenses/LICENSE-2.0 2408 * 2409 * Unless required by applicable law or agreed to in writing, software 2410 * distributed under the License is distributed on an "AS IS" BASIS, 2411 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2412 * 2413 * See the License for the specific language governing permissions and 2414 * limitations under the License. 2415 */ 2416 2417 // !depends ilib.js Locale.js Utils.js 2418 2419 // !data localeinfo 2420 2421 2422 /** 2423 * @class 2424 * Create a new locale info instance. Locale info instances give information about 2425 * the default settings for a particular locale. These settings may be overridden 2426 * by various parts of the code, and should be used as a fall-back setting of last 2427 * resort. <p> 2428 * 2429 * The optional options object holds extra parameters if they are necessary. The 2430 * current list of supported options are: 2431 * 2432 * <ul> 2433 * <li><i>onLoad</i> - a callback function to call when the locale info object is fully 2434 * loaded. When the onLoad option is given, the localeinfo object will attempt to 2435 * load any missing locale data using the ilib loader callback. 2436 * When the constructor is done (even if the data is already preassembled), the 2437 * onLoad function is called with the current instance as a parameter, so this 2438 * callback can be used with preassembled or dynamic loading or a mix of the two. 2439 * 2440 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 2441 * asynchronously. If this option is given as "false", then the "onLoad" 2442 * callback must be given, as the instance returned from this constructor will 2443 * not be usable for a while. 2444 * 2445 * <li><i>loadParams</i> - an object containing parameters to pass to the 2446 * loader callback function when locale data is missing. The parameters are not 2447 * interpretted or modified in any way. They are simply passed along. The object 2448 * may contain any property/value pairs as long as the calling code is in 2449 * agreement with the loader callback function as to what those parameters mean. 2450 * </ul> 2451 * 2452 * If this copy of ilib is pre-assembled and all the data is already available, 2453 * or if the data was already previously loaded, then this constructor will call 2454 * the onLoad callback immediately when the initialization is done. 2455 * If the onLoad option is not given, this class will only attempt to load any 2456 * missing locale data synchronously. 2457 * 2458 * 2459 * @constructor 2460 * @see {ilib.setLoaderCallback} for information about registering a loader callback 2461 * function 2462 * @param {Locale|string=} locale the locale for which the info is sought, or undefined for 2463 * @param {Object=} options the locale for which the info is sought, or undefined for 2464 * the current locale 2465 */ 2466 var LocaleInfo = function(locale, options) { 2467 var sync = true, 2468 loadParams = undefined; 2469 2470 /** 2471 @private 2472 @type {{ 2473 calendar:string, 2474 clock:string, 2475 currency:string, 2476 delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string}, 2477 firstDayOfWeek:number, 2478 meridiems:string, 2479 numfmt:{ 2480 currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string}, 2481 decimalChar:string, 2482 exponential:string, 2483 groupChar:string, 2484 negativenumFmt:string, 2485 negativepctFmt:string, 2486 pctChar:string, 2487 pctFmt:string, 2488 prigroupSize:number, 2489 roundingMode:string, 2490 script:string, 2491 secgroupSize:number 2492 }, 2493 timezone:string, 2494 units:string, 2495 weekendEnd:number, 2496 weekendStart:number, 2497 paperSizes:{regular:string} 2498 }} 2499 */ 2500 this.info = LocaleInfo.defaultInfo; 2501 2502 switch (typeof(locale)) { 2503 case "string": 2504 this.locale = new Locale(locale); 2505 break; 2506 default: 2507 case "undefined": 2508 this.locale = new Locale(); 2509 break; 2510 case "object": 2511 this.locale = locale; 2512 break; 2513 } 2514 2515 if (options) { 2516 if (typeof(options.sync) !== 'undefined') { 2517 sync = (options.sync == true); 2518 } 2519 2520 if (typeof(options.loadParams) !== 'undefined') { 2521 loadParams = options.loadParams; 2522 } 2523 } 2524 2525 if (!LocaleInfo.cache) { 2526 LocaleInfo.cache = {}; 2527 } 2528 2529 Utils.loadData({ 2530 object: LocaleInfo, 2531 locale: this.locale, 2532 name: "localeinfo.json", 2533 sync: sync, 2534 loadParams: loadParams, 2535 callback: ilib.bind(this, function (info) { 2536 if (!info) { 2537 info = LocaleInfo.defaultInfo; 2538 var spec = this.locale.getSpec().replace(/-/g, "_"); 2539 LocaleInfo.cache[spec] = info; 2540 } 2541 this.info = info; 2542 if (options && typeof(options.onLoad) === 'function') { 2543 options.onLoad(this); 2544 } 2545 }) 2546 }); 2547 }; 2548 2549 LocaleInfo.defaultInfo = ilib.data.localeinfo; 2550 LocaleInfo.defaultInfo = LocaleInfo.defaultInfo || { 2551 "calendar": "gregorian", 2552 "clock": "24", 2553 "currency": "USD", 2554 "delimiter": { 2555 "quotationStart": "“", 2556 "quotationEnd": "”", 2557 "alternateQuotationStart": "‘", 2558 "alternateQuotationEnd": "’" 2559 }, 2560 "firstDayOfWeek": 1, 2561 "meridiems": "gregorian", 2562 "numfmt": { 2563 "currencyFormats": { 2564 "common": "{s}{n}", 2565 "commonNegative": "({s}{n})", 2566 "iso": "{s}{n}", 2567 "isoNegative": "({s}{n})" 2568 }, 2569 "decimalChar": ".", 2570 "exponential": "E", 2571 "groupChar": ",", 2572 "negativenumFmt": "-{n}", 2573 "negativepctFmt": "-{n}%", 2574 "pctChar": "%", 2575 "pctFmt": "{n}%", 2576 "prigroupSize": 3, 2577 "roundingMode": "halfdown", 2578 "script": "Latn", 2579 "secgroupSize": 0, 2580 "useNative": false 2581 }, 2582 "timezone": "Etc/UTC", 2583 "units": "metric", 2584 "weekendStart": 6, 2585 "weekendEnd": 0 2586 }; 2587 2588 LocaleInfo.prototype = { 2589 /** 2590 * Return the name of the locale's language in English. 2591 * @returns {string} the name of the locale's language in English 2592 */ 2593 getLanguageName: function () { 2594 return this.info["language.name"]; 2595 }, 2596 2597 /** 2598 * Return the name of the locale's region in English. If the locale 2599 * has no region, this returns undefined. 2600 * 2601 * @returns {string|undefined} the name of the locale's region in English 2602 */ 2603 getRegionName: function () { 2604 return this.info["region.name"]; 2605 }, 2606 2607 /** 2608 * Return whether this locale commonly uses the 12- or the 24-hour clock. 2609 * 2610 * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24" 2611 * if the locale commonly uses a 24-hour clock. 2612 */ 2613 getClock: function() { 2614 return this.info.clock; 2615 }, 2616 2617 /** 2618 * Return the locale that this info object was created with. 2619 * @returns {Locale} The locale spec of the locale used to construct this info instance 2620 */ 2621 getLocale: function () { 2622 return this.locale; 2623 }, 2624 2625 /** 2626 * Return the name of the measuring system that is commonly used in the given locale. 2627 * Valid values are "uscustomary", "imperial", and "metric". 2628 * 2629 * @returns {string} The name of the measuring system commonly used in the locale 2630 */ 2631 getUnits: function () { 2632 return this.info.units; 2633 }, 2634 2635 /** 2636 * Return the name of the calendar that is commonly used in the given locale. 2637 * 2638 * @returns {string} The name of the calendar commonly used in the locale 2639 */ 2640 getCalendar: function () { 2641 return this.info.calendar; 2642 }, 2643 2644 /** 2645 * Return the day of week that starts weeks in the current locale. Days are still 2646 * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars 2647 * should be displayed and weeks calculated with the day of week returned from this 2648 * function as the first day of the week. 2649 * 2650 * @returns {number} the day of the week that starts weeks in the current locale. 2651 */ 2652 getFirstDayOfWeek: function () { 2653 return this.info.firstDayOfWeek; 2654 }, 2655 2656 /** 2657 * Return the day of week that starts weekend in the current locale. Days are still 2658 * numbered the standard way with 0 for Sunday through 6 for Saturday. 2659 * 2660 * @returns {number} the day of the week that starts weeks in the current locale. 2661 */ 2662 getWeekEndStart: function () { 2663 return this.info.weekendStart; 2664 }, 2665 2666 /** 2667 * Return the day of week that starts weekend in the current locale. Days are still 2668 * numbered the standard way with 0 for Sunday through 6 for Saturday. 2669 * 2670 * @returns {number} the day of the week that starts weeks in the current locale. 2671 */ 2672 getWeekEndEnd: function () { 2673 return this.info.weekendEnd; 2674 }, 2675 2676 /** 2677 * Return the default time zone for this locale. Many locales span across multiple 2678 * time zones. In this case, the time zone with the largest population is chosen 2679 * to represent the locale. This is obviously not that accurate, but then again, 2680 * this method's return value should only be used as a default anyways. 2681 * @returns {string} the default time zone for this locale. 2682 */ 2683 getTimeZone: function () { 2684 return this.info.timezone; 2685 }, 2686 2687 /** 2688 * Return the decimal separator for formatted numbers in this locale. 2689 * @returns {string} the decimal separator char 2690 */ 2691 getDecimalSeparator: function () { 2692 return this.info.numfmt.decimalChar; 2693 }, 2694 2695 /** 2696 * Return the decimal separator for formatted numbers in this locale for native script. 2697 * @returns {string} the decimal separator char 2698 */ 2699 getNativeDecimalSeparator: function () { 2700 return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar; 2701 }, 2702 2703 /** 2704 * Return the separator character used to separate groups of digits on the 2705 * integer side of the decimal character. 2706 * @returns {string} the grouping separator char 2707 */ 2708 getGroupingSeparator: function () { 2709 return this.info.numfmt.groupChar; 2710 }, 2711 2712 /** 2713 * Return the separator character used to separate groups of digits on the 2714 * integer side of the decimal character for the native script if present other than the default script. 2715 * @returns {string} the grouping separator char 2716 */ 2717 getNativeGroupingSeparator: function () { 2718 return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar; 2719 }, 2720 2721 /** 2722 * Return the minimum number of digits grouped together on the integer side 2723 * for the first (primary) group. 2724 * In western European cultures, groupings are in 1000s, so the number of digits 2725 * is 3. 2726 * @returns {number} the number of digits in a primary grouping, or 0 for no grouping 2727 */ 2728 getPrimaryGroupingDigits: function () { 2729 return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0; 2730 }, 2731 2732 /** 2733 * Return the minimum number of digits grouped together on the integer side 2734 * for the second or more (secondary) group.<p> 2735 * 2736 * In western European cultures, all groupings are by 1000s, so the secondary 2737 * size should be 0 because there is no secondary size. In general, if this 2738 * method returns 0, then all groupings are of the primary size.<p> 2739 * 2740 * For some other cultures, the first grouping (primary) 2741 * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be 2742 * written as: "1,00,000". 2743 * 2744 * @returns {number} the number of digits in a secondary grouping, or 0 for no 2745 * secondary grouping. 2746 */ 2747 getSecondaryGroupingDigits: function () { 2748 return this.info.numfmt.secgroupSize || 0; 2749 }, 2750 2751 /** 2752 * Return the format template used to format percentages in this locale. 2753 * @returns {string} the format template for formatting percentages 2754 */ 2755 getPercentageFormat: function () { 2756 return this.info.numfmt.pctFmt; 2757 }, 2758 2759 /** 2760 * Return the format template used to format percentages in this locale 2761 * with negative amounts. 2762 * @returns {string} the format template for formatting percentages 2763 */ 2764 getNegativePercentageFormat: function () { 2765 return this.info.numfmt.negativepctFmt; 2766 }, 2767 2768 /** 2769 * Return the symbol used for percentages in this locale. 2770 * @returns {string} the symbol used for percentages in this locale 2771 */ 2772 getPercentageSymbol: function () { 2773 return this.info.numfmt.pctChar || "%"; 2774 }, 2775 2776 /** 2777 * Return the symbol used for exponential in this locale. 2778 * @returns {string} the symbol used for exponential in this locale 2779 */ 2780 getExponential: function () { 2781 return this.info.numfmt.exponential; 2782 }, 2783 2784 /** 2785 * Return the symbol used for exponential in this locale for native script. 2786 * @returns {string} the symbol used for exponential in this locale for native script 2787 */ 2788 getNativeExponential: function () { 2789 return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential; 2790 }, 2791 2792 /** 2793 * Return the symbol used for percentages in this locale for native script. 2794 * @returns {string} the symbol used for percentages in this locale for native script 2795 */ 2796 getNativePercentageSymbol: function () { 2797 return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%"; 2798 2799 }, 2800 /** 2801 * Return the format template used to format negative numbers in this locale. 2802 * @returns {string} the format template for formatting negative numbers 2803 */ 2804 getNegativeNumberFormat: function () { 2805 return this.info.numfmt.negativenumFmt; 2806 }, 2807 2808 /** 2809 * Return an object containing the format templates for formatting currencies 2810 * in this locale. The object has a number of properties in it that each are 2811 * a particular style of format. Normally, this contains a "common" and an "iso" 2812 * style, but may contain others in the future. 2813 * @returns {Object} an object containing the format templates for currencies 2814 */ 2815 getCurrencyFormats: function () { 2816 return this.info.numfmt.currencyFormats; 2817 }, 2818 2819 /** 2820 * Return the currency that is legal in the locale, or which is most commonly 2821 * used in regular commerce. 2822 * @returns {string} the ISO 4217 code for the currency of this locale 2823 */ 2824 getCurrency: function () { 2825 return this.info.currency; 2826 }, 2827 2828 /** 2829 * Return a string that describes the style of digits used by this locale. 2830 * Possible return values are: 2831 * <ul> 2832 * <li><i>western</i> - uses the regular western 10-based digits 0 through 9 2833 * <li><i>optional</i> - native 10-based digits exist, but in modern usage, 2834 * this locale most often uses western digits 2835 * <li><i>native</i> - native 10-based native digits exist and are used 2836 * regularly by this locale 2837 * <li><i>custom</i> - uses native digits by default that are not 10-based 2838 * </ul> 2839 * @returns {string} string that describes the style of digits used in this locale 2840 */ 2841 getDigitsStyle: function () { 2842 if (this.info.numfmt.useNative) { 2843 return "native"; 2844 } 2845 if (typeof(this.info.native_numfmt) !== 'undefined') { 2846 return "optional"; 2847 } 2848 return "western"; 2849 }, 2850 2851 /** 2852 * Return the digits of the default script if they are defined. 2853 * If not defined, the default should be the regular "Arabic numerals" 2854 * used in the Latin script. (0-9) 2855 * @returns {string|undefined} the digits used in the default script 2856 */ 2857 getDigits: function () { 2858 return this.info.numfmt.digits; 2859 }, 2860 2861 /** 2862 * Return the digits of the native script if they are defined. 2863 * @returns {string|undefined} the digits used in the default script 2864 */ 2865 getNativeDigits: function () { 2866 return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits); 2867 }, 2868 2869 /** 2870 * If this locale typically uses a different type of rounding for numeric 2871 * formatting other than halfdown, especially for currency, then it can be 2872 * specified in the localeinfo. If the locale uses the default, then this 2873 * method returns undefined. The locale's rounding method overrides the 2874 * rounding method for the currency itself, which can sometimes shared 2875 * between various locales so it is less specific. 2876 * @returns {string} the name of the rounding mode typically used in this 2877 * locale, or "halfdown" if the locale does not override the default 2878 */ 2879 getRoundingMode: function () { 2880 return this.info.numfmt.roundingMode; 2881 }, 2882 2883 /** 2884 * Return the default script used to write text in the language of this 2885 * locale. Text for most languages is written in only one script, but there 2886 * are some languages where the text can be written in a number of scripts, 2887 * depending on a variety of things such as the region, ethnicity, religion, 2888 * etc. of the author. This method returns the default script for the 2889 * locale, in which the language is most commonly written.<p> 2890 * 2891 * The script is returned as an ISO 15924 4-letter code. 2892 * 2893 * @returns {string} the ISO 15924 code for the default script used to write 2894 * text in this locale 2895 */ 2896 getDefaultScript: function() { 2897 return (this.info.scripts) ? this.info.scripts[0] : "Latn"; 2898 }, 2899 2900 /** 2901 * Return the script used for the current locale. If the current locale 2902 * explicitly defines a script, then this script is returned. If not, then 2903 * the default script for the locale is returned. 2904 * 2905 * @see LocaleInfo.getDefaultScript 2906 * @returns {string} the ISO 15924 code for the script used to write 2907 * text in this locale 2908 */ 2909 getScript: function() { 2910 return this.locale.getScript() || this.getDefaultScript(); 2911 }, 2912 2913 /** 2914 * Return an array of script codes which are used to write text in the current 2915 * language. Text for most languages is written in only one script, but there 2916 * are some languages where the text can be written in a number of scripts, 2917 * depending on a variety of things such as the region, ethnicity, religion, 2918 * etc. of the author. This method returns an array of script codes in which 2919 * the language is commonly written. 2920 * 2921 * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used 2922 * to write text in this language 2923 */ 2924 getAllScripts: function() { 2925 return this.info.scripts || ["Latn"]; 2926 }, 2927 2928 /** 2929 * Return the default style of meridiems used in this locale. Meridiems are 2930 * times of day like AM/PM. In a few locales with some calendars, for example 2931 * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be 2932 * split into different segments than simple AM/PM as in the Gregorian 2933 * calendar. Only a few locales are like that. For most locales, formatting 2934 * a Gregorian date will use the regular Gregorian AM/PM meridiems. 2935 * 2936 * @returns {string} the default meridiems style used in this locale. Possible 2937 * values are "gregorian", "chinese", and "ethiopic" 2938 */ 2939 getMeridiemsStyle: function () { 2940 return this.info.meridiems || "gregorian"; 2941 }, 2942 /** 2943 * Return the default PaperSize information in this locale. 2944 * @returns {string} default PaperSize in this locale 2945 */ 2946 getPaperSize: function () { 2947 return this.info.paperSizes.regular; 2948 }, 2949 /** 2950 * Return the default Delimiter QuotationStart information in this locale. 2951 * @returns {string} default QuotationStart in this locale 2952 */ 2953 getDelimiterQuotationStart: function () { 2954 return this.info.delimiter.quotationStart; 2955 }, 2956 /** 2957 * Return the default Delimiter QuotationEnd information in this locale. 2958 * @returns {string} default QuotationEnd in this locale 2959 */ 2960 getDelimiterQuotationEnd: function () { 2961 return this.info.delimiter.quotationEnd; 2962 } 2963 }; 2964 2965 2966 2967 /*< IDate.js */ 2968 /* 2969 * IDate.js - Represent a date in any calendar. This class is subclassed for each 2970 * calendar and includes some shared functionality. 2971 * 2972 * Copyright © 2012-2015, JEDLSoft 2973 * 2974 * Licensed under the Apache License, Version 2.0 (the "License"); 2975 * you may not use this file except in compliance with the License. 2976 * You may obtain a copy of the License at 2977 * 2978 * http://www.apache.org/licenses/LICENSE-2.0 2979 * 2980 * Unless required by applicable law or agreed to in writing, software 2981 * distributed under the License is distributed on an "AS IS" BASIS, 2982 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2983 * 2984 * See the License for the specific language governing permissions and 2985 * limitations under the License. 2986 */ 2987 2988 /* !depends LocaleInfo.js */ 2989 2990 2991 /** 2992 * @class 2993 * Superclass for all the calendar date classes that contains shared 2994 * functionality. This class is never instantiated on its own. Instead, 2995 * you should use the {@link DateFactory} function to manufacture a new 2996 * instance of a subclass of IDate. This class is called IDate for "ilib 2997 * date" so that it does not conflict with the built-in Javascript Date 2998 * class. 2999 * 3000 * @private 3001 * @constructor 3002 * @param {Object=} options The date components to initialize this date with 3003 */ 3004 var IDate = function(options) { 3005 }; 3006 3007 /* place for the subclasses to put their constructors so that the factory method 3008 * can find them. Do this to add your date after it's defined: 3009 * IDate._constructors["mytype"] = IDate.MyTypeConstructor; 3010 */ 3011 IDate._constructors = {}; 3012 3013 IDate.prototype = { 3014 getType: function() { 3015 return "date"; 3016 }, 3017 3018 /** 3019 * Return the unix time equivalent to this date instance. Unix time is 3020 * the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This 3021 * method only returns a valid number for dates between midnight, 3022 * Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when 3023 * the unix time runs out. If this instance encodes a date outside of that range, 3024 * this method will return -1. For date types that are not Gregorian, the point 3025 * in time represented by this date object will only give a return value if it 3026 * is in the correct range in the Gregorian calendar as given previously. 3027 * 3028 * @return {number} a number giving the unix time, or -1 if the date is outside the 3029 * valid unix time range 3030 */ 3031 getTime: function() { 3032 return this.rd.getTime(); 3033 }, 3034 3035 /** 3036 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 3037 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 3038 * (or the type "time_t" in C/C++) is only encoded with an unsigned 32 bit integer, and thus 3039 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 3040 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 3041 * after Jan 1, 1970, and even more interestingly, 100 million days worth of time before 3042 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 3043 * range. If this instance encodes a date outside of that range, this method will return 3044 * NaN. 3045 * 3046 * @return {number} a number giving the extended unix time, or Nan if the date is outside 3047 * the valid extended unix time range 3048 */ 3049 getTimeExtended: function() { 3050 return this.rd.getTimeExtended(); 3051 }, 3052 3053 /** 3054 * Set the time of this instance according to the given unix time. Unix time is 3055 * the number of milliseconds since midnight on Jan 1, 1970. 3056 * 3057 * @param {number} millis the unix time to set this date to in milliseconds 3058 */ 3059 setTime: function(millis) { 3060 this.rd = this.newRd({ 3061 unixtime: millis, 3062 cal: this.cal 3063 }); 3064 this._calcDateComponents(); 3065 }, 3066 3067 getDays: function() { 3068 return this.day; 3069 }, 3070 getMonths: function() { 3071 return this.month; 3072 }, 3073 getYears: function() { 3074 return this.year; 3075 }, 3076 getHours: function() { 3077 return this.hour; 3078 }, 3079 getMinutes: function() { 3080 return this.minute; 3081 }, 3082 getSeconds: function() { 3083 return this.second; 3084 }, 3085 getMilliseconds: function() { 3086 return this.millisecond; 3087 }, 3088 getEra: function() { 3089 return (this.year < 1) ? -1 : 1; 3090 }, 3091 3092 setDays: function(day) { 3093 this.day = parseInt(day, 10) || 1; 3094 this.rd._setDateComponents(this); 3095 }, 3096 setMonths: function(month) { 3097 this.month = parseInt(month, 10) || 1; 3098 this.rd._setDateComponents(this); 3099 }, 3100 setYears: function(year) { 3101 this.year = parseInt(year, 10) || 0; 3102 this.rd._setDateComponents(this); 3103 }, 3104 3105 setHours: function(hour) { 3106 this.hour = parseInt(hour, 10) || 0; 3107 this.rd._setDateComponents(this); 3108 }, 3109 setMinutes: function(minute) { 3110 this.minute = parseInt(minute, 10) || 0; 3111 this.rd._setDateComponents(this); 3112 }, 3113 setSeconds: function(second) { 3114 this.second = parseInt(second, 10) || 0; 3115 this.rd._setDateComponents(this); 3116 }, 3117 setMilliseconds: function(milli) { 3118 this.millisecond = parseInt(milli, 10) || 0; 3119 this.rd._setDateComponents(this); 3120 }, 3121 3122 /** 3123 * Return a new date instance in the current calendar that represents the first instance 3124 * of the given day of the week before the current date. The day of the week is encoded 3125 * as a number where 0 = Sunday, 1 = Monday, etc. 3126 * 3127 * @param {number} dow the day of the week before the current date that is being sought 3128 * @return {IDate} the date being sought 3129 */ 3130 before: function (dow) { 3131 return new this.constructor({ 3132 rd: this.rd.before(dow, this.offset), 3133 timezone: this.timezone 3134 }); 3135 }, 3136 3137 /** 3138 * Return a new date instance in the current calendar that represents the first instance 3139 * of the given day of the week after the current date. The day of the week is encoded 3140 * as a number where 0 = Sunday, 1 = Monday, etc. 3141 * 3142 * @param {number} dow the day of the week after the current date that is being sought 3143 * @return {IDate} the date being sought 3144 */ 3145 after: function (dow) { 3146 return new this.constructor({ 3147 rd: this.rd.after(dow, this.offset), 3148 timezone: this.timezone 3149 }); 3150 }, 3151 3152 /** 3153 * Return a new Gregorian date instance that represents the first instance of the 3154 * given day of the week on or before the current date. The day of the week is encoded 3155 * as a number where 0 = Sunday, 1 = Monday, etc. 3156 * 3157 * @param {number} dow the day of the week on or before the current date that is being sought 3158 * @return {IDate} the date being sought 3159 */ 3160 onOrBefore: function (dow) { 3161 return new this.constructor({ 3162 rd: this.rd.onOrBefore(dow, this.offset), 3163 timezone: this.timezone 3164 }); 3165 }, 3166 3167 /** 3168 * Return a new Gregorian date instance that represents the first instance of the 3169 * given day of the week on or after the current date. The day of the week is encoded 3170 * as a number where 0 = Sunday, 1 = Monday, etc. 3171 * 3172 * @param {number} dow the day of the week on or after the current date that is being sought 3173 * @return {IDate} the date being sought 3174 */ 3175 onOrAfter: function (dow) { 3176 return new this.constructor({ 3177 rd: this.rd.onOrAfter(dow, this.offset), 3178 timezone: this.timezone 3179 }); 3180 }, 3181 3182 /** 3183 * Return a Javascript Date object that is equivalent to this date 3184 * object. 3185 * 3186 * @return {Date|undefined} a javascript Date object 3187 */ 3188 getJSDate: function() { 3189 var unix = this.rd.getTimeExtended(); 3190 return isNaN(unix) ? undefined : new Date(unix); 3191 }, 3192 3193 /** 3194 * Return the Rata Die (fixed day) number of this date. 3195 * 3196 * @protected 3197 * @return {number} the rd date as a number 3198 */ 3199 getRataDie: function() { 3200 return this.rd.getRataDie(); 3201 }, 3202 3203 /** 3204 * Set the date components of this instance based on the given rd. 3205 * @protected 3206 * @param {number} rd the rata die date to set 3207 */ 3208 setRd: function (rd) { 3209 this.rd = this.newRd({ 3210 rd: rd, 3211 cal: this.cal 3212 }); 3213 this._calcDateComponents(); 3214 }, 3215 3216 /** 3217 * Return the Julian Day equivalent to this calendar date as a number. 3218 * 3219 * @return {number} the julian date equivalent of this date 3220 */ 3221 getJulianDay: function() { 3222 return this.rd.getJulianDay(); 3223 }, 3224 3225 /** 3226 * Set the date of this instance using a Julian Day. 3227 * @param {number|JulianDay} date the Julian Day to use to set this date 3228 */ 3229 setJulianDay: function (date) { 3230 this.rd = this.newRd({ 3231 julianday: (typeof(date) === 'object') ? date.getDate() : date, 3232 cal: this.cal 3233 }); 3234 this._calcDateComponents(); 3235 }, 3236 3237 /** 3238 * Return the time zone associated with this date, or 3239 * undefined if none was specified in the constructor. 3240 * 3241 * @return {string|undefined} the name of the time zone for this date instance 3242 */ 3243 getTimeZone: function() { 3244 return this.timezone || "local"; 3245 }, 3246 3247 /** 3248 * Set the time zone associated with this date. 3249 * @param {string=} tzName the name of the time zone to set into this date instance, 3250 * or "undefined" to unset the time zone 3251 */ 3252 setTimeZone: function (tzName) { 3253 if (!tzName || tzName === "") { 3254 // same as undefining it 3255 this.timezone = undefined; 3256 this.tz = undefined; 3257 } else if (typeof(tzName) === 'string') { 3258 this.timezone = tzName; 3259 this.tz = undefined; 3260 // assuming the same UTC time, but a new time zone, now we have to 3261 // recalculate what the date components are 3262 this._calcDateComponents(); 3263 } 3264 }, 3265 3266 /** 3267 * Return the rd number of the first Sunday of the given ISO year. 3268 * @protected 3269 * @param {number} year the year for which the first Sunday is being sought 3270 * @return {number} the rd of the first Sunday of the ISO year 3271 */ 3272 firstSunday: function (year) { 3273 var firstDay = this.newRd({ 3274 year: year, 3275 month: 1, 3276 day: 1, 3277 hour: 0, 3278 minute: 0, 3279 second: 0, 3280 millisecond: 0, 3281 cal: this.cal 3282 }); 3283 var firstThu = this.newRd({ 3284 rd: firstDay.onOrAfter(4), 3285 cal: this.cal 3286 }); 3287 return firstThu.before(0); 3288 }, 3289 3290 /** 3291 * Return the ISO 8601 week number in the current year for the current date. The week 3292 * number ranges from 0 to 55, as some years have 55 weeks assigned to them in some 3293 * calendars. 3294 * 3295 * @return {number} the week number for the current date 3296 */ 3297 getWeekOfYear: function() { 3298 var rd = Math.floor(this.rd.getRataDie()); 3299 var year = this._calcYear(rd + this.offset); 3300 var yearStart = this.firstSunday(year); 3301 var nextYear; 3302 3303 // if we have a January date, it may be in this ISO year or the previous year 3304 if (rd < yearStart) { 3305 yearStart = this.firstSunday(year-1); 3306 } else { 3307 // if we have a late December date, it may be in this ISO year, or the next year 3308 nextYear = this.firstSunday(year+1); 3309 if (rd >= nextYear) { 3310 yearStart = nextYear; 3311 } 3312 } 3313 3314 return Math.floor((rd-yearStart)/7) + 1; 3315 }, 3316 3317 /** 3318 * Return the ordinal number of the week within the month. The first week of a month is 3319 * the first one that contains 4 or more days in that month. If any days precede this 3320 * first week, they are marked as being in week 0. This function returns values from 0 3321 * through 6.<p> 3322 * 3323 * The locale is a required parameter because different locales that use the same 3324 * Gregorian calendar consider different days of the week to be the beginning of 3325 * the week. This can affect the week of the month in which some days are located. 3326 * 3327 * @param {Locale|string} locale the locale or locale spec to use when figuring out 3328 * the first day of the week 3329 * @return {number} the ordinal number of the week within the current month 3330 */ 3331 getWeekOfMonth: function(locale) { 3332 var li = new LocaleInfo(locale); 3333 3334 var first = this.newRd({ 3335 year: this._calcYear(this.rd.getRataDie()+this.offset), 3336 month: this.getMonths(), 3337 day: 1, 3338 hour: 0, 3339 minute: 0, 3340 second: 0, 3341 millisecond: 0, 3342 cal: this.cal 3343 }); 3344 var weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 3345 3346 if (weekStart - first.getRataDie() > 3) { 3347 // if the first week has 4 or more days in it of the current month, then consider 3348 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 3349 // one week earlier. 3350 weekStart -= 7; 3351 } 3352 return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1; 3353 } 3354 }; 3355 3356 3357 /*< MathUtils.js */ 3358 /* 3359 * MathUtils.js - Misc math utility routines 3360 * 3361 * Copyright © 2013-2015, JEDLSoft 3362 * 3363 * Licensed under the Apache License, Version 2.0 (the "License"); 3364 * you may not use this file except in compliance with the License. 3365 * You may obtain a copy of the License at 3366 * 3367 * http://www.apache.org/licenses/LICENSE-2.0 3368 * 3369 * Unless required by applicable law or agreed to in writing, software 3370 * distributed under the License is distributed on an "AS IS" BASIS, 3371 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3372 * 3373 * See the License for the specific language governing permissions and 3374 * limitations under the License. 3375 */ 3376 3377 var MathUtils = {}; 3378 3379 /** 3380 * Return the sign of the given number. If the sign is negative, this function 3381 * returns -1. If the sign is positive or zero, this function returns 1. 3382 * @static 3383 * @param {number} num the number to test 3384 * @return {number} -1 if the number is negative, and 1 otherwise 3385 */ 3386 MathUtils.signum = function (num) { 3387 var n = num; 3388 if (typeof(num) === 'string') { 3389 n = parseInt(num, 10); 3390 } else if (typeof(num) !== 'number') { 3391 return 1; 3392 } 3393 return (n < 0) ? -1 : 1; 3394 }; 3395 3396 /** 3397 * @static 3398 * @protected 3399 * @param {number} num number to round 3400 * @return {number} rounded number 3401 */ 3402 MathUtils.floor = function (num) { 3403 return Math.floor(num); 3404 }; 3405 3406 /** 3407 * @static 3408 * @protected 3409 * @param {number} num number to round 3410 * @return {number} rounded number 3411 */ 3412 MathUtils.ceiling = function (num) { 3413 return Math.ceil(num); 3414 }; 3415 3416 /** 3417 * @static 3418 * @protected 3419 * @param {number} num number to round 3420 * @return {number} rounded number 3421 */ 3422 MathUtils.down = function (num) { 3423 return (num < 0) ? Math.ceil(num) : Math.floor(num); 3424 }; 3425 3426 /** 3427 * @static 3428 * @protected 3429 * @param {number} num number to round 3430 * @return {number} rounded number 3431 */ 3432 MathUtils.up = function (num) { 3433 return (num < 0) ? Math.floor(num) : Math.ceil(num); 3434 }; 3435 3436 /** 3437 * @static 3438 * @protected 3439 * @param {number} num number to round 3440 * @return {number} rounded number 3441 */ 3442 MathUtils.halfup = function (num) { 3443 return (num < 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3444 }; 3445 3446 /** 3447 * @static 3448 * @protected 3449 * @param {number} num number to round 3450 * @return {number} rounded number 3451 */ 3452 MathUtils.halfdown = function (num) { 3453 return (num < 0) ? Math.floor(num + 0.5) : Math.ceil(num - 0.5); 3454 }; 3455 3456 /** 3457 * @static 3458 * @protected 3459 * @param {number} num number to round 3460 * @return {number} rounded number 3461 */ 3462 MathUtils.halfeven = function (num) { 3463 return (Math.floor(num) % 2 === 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3464 }; 3465 3466 /** 3467 * @static 3468 * @protected 3469 * @param {number} num number to round 3470 * @return {number} rounded number 3471 */ 3472 MathUtils.halfodd = function (num) { 3473 return (Math.floor(num) % 2 !== 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3474 }; 3475 3476 /** 3477 * Do a proper modulo function. The Javascript % operator will give the truncated 3478 * division algorithm, but for calendrical calculations, we need the Euclidean 3479 * division algorithm where the remainder of any division, whether the dividend 3480 * is negative or not, is always a positive number in the range [0, modulus).<p> 3481 * 3482 * 3483 * @static 3484 * @param {number} dividend the number being divided 3485 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 3486 * @return the remainder of dividing the dividend by the modulus. 3487 */ 3488 MathUtils.mod = function (dividend, modulus) { 3489 if (modulus == 0) { 3490 return 0; 3491 } 3492 var x = dividend % modulus; 3493 return (x < 0) ? x + modulus : x; 3494 }; 3495 3496 /** 3497 * Do a proper adjusted modulo function. The Javascript % operator will give the truncated 3498 * division algorithm, but for calendrical calculations, we need the Euclidean 3499 * division algorithm where the remainder of any division, whether the dividend 3500 * is negative or not, is always a positive number in the range (0, modulus]. The adjusted 3501 * modulo function differs from the regular modulo function in that when the remainder is 3502 * zero, the modulus should be returned instead.<p> 3503 * 3504 * 3505 * @static 3506 * @param {number} dividend the number being divided 3507 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 3508 * @return the remainder of dividing the dividend by the modulus. 3509 */ 3510 MathUtils.amod = function (dividend, modulus) { 3511 if (modulus == 0) { 3512 return 0; 3513 } 3514 var x = dividend % modulus; 3515 return (x <= 0) ? x + modulus : x; 3516 }; 3517 3518 3519 3520 /*< IString.js */ 3521 /* 3522 * IString.js - ilib string subclass definition 3523 * 3524 * Copyright © 2012-2015, JEDLSoft 3525 * 3526 * Licensed under the Apache License, Version 2.0 (the "License"); 3527 * you may not use this file except in compliance with the License. 3528 * You may obtain a copy of the License at 3529 * 3530 * http://www.apache.org/licenses/LICENSE-2.0 3531 * 3532 * Unless required by applicable law or agreed to in writing, software 3533 * distributed under the License is distributed on an "AS IS" BASIS, 3534 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3535 * 3536 * See the License for the specific language governing permissions and 3537 * limitations under the License. 3538 */ 3539 3540 // !depends ilib.js Utils.js Locale.js MathUtils.js 3541 3542 // !data plurals 3543 3544 3545 /** 3546 * @class 3547 * Create a new ilib string instance. This string inherits from and 3548 * extends the Javascript String class. It can be 3549 * used almost anywhere that a normal Javascript string is used, though in 3550 * some instances you will need to call the {@link #toString} method when 3551 * a built-in Javascript string is needed. The formatting methods are 3552 * methods that are not in the intrinsic String class and are most useful 3553 * when localizing strings in an app or web site in combination with 3554 * the ResBundle class.<p> 3555 * 3556 * This class is named IString ("ilib string") so as not to conflict with the 3557 * built-in Javascript String class. 3558 * 3559 * @constructor 3560 * @param {string|IString=} string initialize this instance with this string 3561 */ 3562 var IString = function (string) { 3563 if (typeof(string) === 'object') { 3564 if (string instanceof IString) { 3565 this.str = string.str; 3566 } else { 3567 this.str = string.toString(); 3568 } 3569 } else if (typeof(string) === 'string') { 3570 this.str = new String(string); 3571 } else { 3572 this.str = ""; 3573 } 3574 this.length = this.str.length; 3575 this.cpLength = -1; 3576 this.localeSpec = ilib.getLocale(); 3577 }; 3578 3579 /** 3580 * Return true if the given character is a Unicode surrogate character, 3581 * either high or low. 3582 * 3583 * @private 3584 * @static 3585 * @param {string} ch character to check 3586 * @return {boolean} true if the character is a surrogate 3587 */ 3588 IString._isSurrogate = function (ch) { 3589 var n = ch.charCodeAt(0); 3590 return ((n >= 0xDC00 && n <= 0xDFFF) || (n >= 0xD800 && n <= 0xDBFF)); 3591 }; 3592 3593 /** 3594 * Convert a UCS-4 code point to a Javascript string. The codepoint can be any valid 3595 * UCS-4 Unicode character, including supplementary characters. Standard Javascript 3596 * only supports supplementary characters using the UTF-16 encoding, which has 3597 * values in the range 0x0000-0xFFFF. String.fromCharCode() will only 3598 * give you a string containing 16-bit characters, and will not properly convert 3599 * the code point for a supplementary character (which has a value > 0xFFFF) into 3600 * two UTF-16 surrogate characters. Instead, it will just just give you whatever 3601 * single character happens to be the same as your code point modulo 0x10000, which 3602 * is almost never what you want.<p> 3603 * 3604 * Similarly, that means if you use String.charCodeAt() 3605 * you will only retrieve a 16-bit value, which may possibly be a single 3606 * surrogate character that is part of a surrogate pair representing a character 3607 * in the supplementary plane. It will not give you a code point. Use 3608 * IString.codePointAt() to access code points in a string, or use 3609 * an iterator to walk through the code points in a string. 3610 * 3611 * @static 3612 * @param {number} codepoint UCS-4 code point to convert to a character 3613 * @return {string} a string containing the character represented by the codepoint 3614 */ 3615 IString.fromCodePoint = function (codepoint) { 3616 if (codepoint < 0x10000) { 3617 return String.fromCharCode(codepoint); 3618 } else { 3619 var high = Math.floor(codepoint / 0x10000) - 1; 3620 var low = codepoint & 0xFFFF; 3621 3622 return String.fromCharCode(0xD800 | ((high & 0x000F) << 6) | ((low & 0xFC00) >> 10)) + 3623 String.fromCharCode(0xDC00 | (low & 0x3FF)); 3624 } 3625 }; 3626 3627 /** 3628 * Convert the character or the surrogate pair at the given 3629 * index into the intrinsic Javascript string to a Unicode 3630 * UCS-4 code point. 3631 * 3632 * @static 3633 * @param {string} str string to get the code point from 3634 * @param {number} index index into the string 3635 * @return {number} code point of the character at the 3636 * given index into the string 3637 */ 3638 IString.toCodePoint = function(str, index) { 3639 if (!str || str.length === 0) { 3640 return -1; 3641 } 3642 var code = -1, high = str.charCodeAt(index); 3643 if (high >= 0xD800 && high <= 0xDBFF) { 3644 if (str.length > index+1) { 3645 var low = str.charCodeAt(index+1); 3646 if (low >= 0xDC00 && low <= 0xDFFF) { 3647 code = (((high & 0x3C0) >> 6) + 1) << 16 | 3648 (((high & 0x3F) << 10) | (low & 0x3FF)); 3649 } 3650 } 3651 } else { 3652 code = high; 3653 } 3654 3655 return code; 3656 }; 3657 3658 /** 3659 * Load the plural the definitions of plurals for the locale. 3660 * @param {boolean=} sync 3661 * @param {Locale|string=} locale 3662 * @param {Object=} loadParams 3663 * @param {function(*)=} onLoad 3664 */ 3665 IString.loadPlurals = function (sync, locale, loadParams, onLoad) { 3666 var loc; 3667 if (locale) { 3668 loc = (typeof(locale) === 'string') ? new Locale(locale) : locale; 3669 } else { 3670 loc = new Locale(ilib.getLocale()); 3671 } 3672 var spec = loc.getLanguage(); 3673 if (!ilib.data["plurals_" + spec]) { 3674 Utils.loadData({ 3675 name: "plurals.json", 3676 object: IString, 3677 locale: loc, 3678 sync: sync, 3679 loadParams: loadParams, 3680 callback: ilib.bind(this, function(plurals) { 3681 if (!plurals) { 3682 IString.cache[spec] = {}; 3683 } 3684 ilib.data["plurals_" + spec] = plurals || {}; 3685 if (onLoad && typeof(onLoad) === 'function') { 3686 onLoad(ilib.data["plurals_" + spec]); 3687 } 3688 }) 3689 }); 3690 } else { 3691 if (onLoad && typeof(onLoad) === 'function') { 3692 onLoad(ilib.data["plurals_" + spec]); 3693 } 3694 } 3695 }; 3696 3697 /** 3698 * @private 3699 * @static 3700 */ 3701 IString._fncs = { 3702 /** 3703 * @private 3704 * @param {Object} obj 3705 * @return {string|undefined} 3706 */ 3707 firstProp: function (obj) { 3708 for (var p in obj) { 3709 if (p && obj[p]) { 3710 return p; 3711 } 3712 } 3713 return undefined; // should never get here 3714 }, 3715 3716 /** 3717 * @private 3718 * @param {Object} obj 3719 * @return {string|undefined} 3720 */ 3721 firstPropRule: function (obj) { 3722 if (Object.prototype.toString.call(obj) === '[object Array]') { 3723 return "inrange"; 3724 } else if (Object.prototype.toString.call(obj) === '[object Object]') { 3725 for (var p in obj) { 3726 if (p && obj[p]) { 3727 return p; 3728 } 3729 } 3730 3731 } 3732 return undefined; // should never get here 3733 }, 3734 3735 /** 3736 * @private 3737 * @param {Object} obj 3738 * @param {number|Object} n 3739 * @return {?} 3740 */ 3741 getValue: function (obj, n) { 3742 if (typeof(obj) === 'object') { 3743 var subrule = IString._fncs.firstPropRule(obj); 3744 if (subrule === "inrange") { 3745 return IString._fncs[subrule](obj, n); 3746 } 3747 return IString._fncs[subrule](obj[subrule], n); 3748 } else if (typeof(obj) === 'string') { 3749 if (typeof(n) === 'object'){ 3750 return n[obj]; 3751 } 3752 return n; 3753 } else { 3754 return obj; 3755 } 3756 }, 3757 3758 /** 3759 * @private 3760 * @param {number|Object} n 3761 * @param {Array.<number|Array.<number>>|Object} range 3762 * @return {boolean} 3763 */ 3764 matchRangeContinuous: function(n, range) { 3765 3766 for (var num in range) { 3767 if (typeof(num) !== 'undefined' && typeof(range[num]) !== 'undefined') { 3768 var obj = range[num]; 3769 if (typeof(obj) === 'number') { 3770 if (n === range[num]) { 3771 return true; 3772 } else if (n >= range[0] && n <= range[1]) { 3773 return true; 3774 } 3775 } else if (Object.prototype.toString.call(obj) === '[object Array]') { 3776 if (n >= obj[0] && n <= obj[1]) { 3777 return true; 3778 } 3779 } 3780 } 3781 } 3782 return false; 3783 }, 3784 3785 /** 3786 * @private 3787 * @param {*} number 3788 * @return {Object} 3789 */ 3790 calculateNumberDigits: function(number) { 3791 var numberToString = number.toString(); 3792 var parts = []; 3793 var numberDigits = {}; 3794 var operandSymbol = {}; 3795 var integerPart, decimalPartLength, decimalPart; 3796 3797 if (numberToString.indexOf('.') !== -1) { //decimal 3798 parts = numberToString.split('.', 2); 3799 numberDigits.integerPart = parseInt(parts[0], 10); 3800 numberDigits.decimalPartLength = parts[1].length; 3801 numberDigits.decimalPart = parseInt(parts[1], 10); 3802 3803 operandSymbol.n = parseFloat(number); 3804 operandSymbol.i = numberDigits.integerPart; 3805 operandSymbol.v = numberDigits.decimalPartLength; 3806 operandSymbol.w = numberDigits.decimalPartLength; 3807 operandSymbol.f = numberDigits.decimalPart; 3808 operandSymbol.t = numberDigits.decimalPart; 3809 3810 } else { 3811 numberDigits.integerPart = number; 3812 numberDigits.decimalPartLength = 0; 3813 numberDigits.decimalPart = 0; 3814 3815 operandSymbol.n = parseInt(number, 10); 3816 operandSymbol.i = numberDigits.integerPart; 3817 operandSymbol.v = 0; 3818 operandSymbol.w = 0; 3819 operandSymbol.f = 0; 3820 operandSymbol.t = 0; 3821 3822 } 3823 return operandSymbol 3824 }, 3825 3826 /** 3827 * @private 3828 * @param {number|Object} n 3829 * @param {Array.<number|Array.<number>>|Object} range 3830 * @return {boolean} 3831 */ 3832 matchRange: function(n, range) { 3833 return IString._fncs.matchRangeContinuous(n, range); 3834 }, 3835 3836 /** 3837 * @private 3838 * @param {Object} rule 3839 * @param {number} n 3840 * @return {boolean} 3841 */ 3842 is: function(rule, n) { 3843 var left = IString._fncs.getValue(rule[0], n); 3844 var right = IString._fncs.getValue(rule[1], n); 3845 return left == right; 3846 }, 3847 3848 /** 3849 * @private 3850 * @param {Object} rule 3851 * @param {number} n 3852 * @return {boolean} 3853 */ 3854 isnot: function(rule, n) { 3855 return IString._fncs.getValue(rule[0], n) != IString._fncs.getValue(rule[1], n); 3856 }, 3857 3858 /** 3859 * @private 3860 * @param {Object} rule 3861 * @param {number|Object} n 3862 * @return {boolean} 3863 */ 3864 inrange: function(rule, n) { 3865 if (typeof(rule[0]) === 'number') { 3866 if(typeof(n) === 'object') { 3867 return IString._fncs.matchRange(n.n,rule); 3868 } 3869 return IString._fncs.matchRange(n,rule); 3870 } else if (typeof(rule[0]) === 'undefined') { 3871 var subrule = IString._fncs.firstPropRule(rule); 3872 return IString._fncs[subrule](rule[subrule], n); 3873 } else { 3874 return IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3875 } 3876 }, 3877 /** 3878 * @private 3879 * @param {Object} rule 3880 * @param {number} n 3881 * @return {boolean} 3882 */ 3883 notin: function(rule, n) { 3884 return !IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3885 }, 3886 3887 /** 3888 * @private 3889 * @param {Object} rule 3890 * @param {number} n 3891 * @return {boolean} 3892 */ 3893 within: function(rule, n) { 3894 return IString._fncs.matchRangeContinuous(IString._fncs.getValue(rule[0], n), rule[1]); 3895 }, 3896 3897 /** 3898 * @private 3899 * @param {Object} rule 3900 * @param {number} n 3901 * @return {number} 3902 */ 3903 mod: function(rule, n) { 3904 return MathUtils.mod(IString._fncs.getValue(rule[0], n), IString._fncs.getValue(rule[1], n)); 3905 }, 3906 3907 /** 3908 * @private 3909 * @param {Object} rule 3910 * @param {number} n 3911 * @return {number} 3912 */ 3913 n: function(rule, n) { 3914 return n; 3915 }, 3916 3917 /** 3918 * @private 3919 * @param {Object} rule 3920 * @param {number|Object} n 3921 * @return {boolean} 3922 */ 3923 or: function(rule, n) { 3924 var ruleLength = rule.length; 3925 var result, i; 3926 for (i=0; i < ruleLength; i++) { 3927 result = IString._fncs.getValue(rule[i], n); 3928 if (result) { 3929 return true; 3930 } 3931 } 3932 return false; 3933 }, 3934 /** 3935 * @private 3936 * @param {Object} rule 3937 * @param {number|Object} n 3938 * @return {boolean} 3939 */ 3940 and: function(rule, n) { 3941 var ruleLength = rule.length; 3942 var result, i; 3943 for (i=0; i < ruleLength; i++) { 3944 result= IString._fncs.getValue(rule[i], n); 3945 if (!result) { 3946 return false; 3947 } 3948 } 3949 return true; 3950 }, 3951 /** 3952 * @private 3953 * @param {Object} rule 3954 * @param {number|Object} n 3955 * @return {boolean} 3956 */ 3957 eq: function(rule, n) { 3958 var valueLeft = IString._fncs.getValue(rule[0], n); 3959 var valueRight; 3960 3961 if (typeof(rule[0]) === 'string') { 3962 if (typeof(n) === 'object'){ 3963 valueRight = n[rule[0]]; 3964 if (typeof(rule[1])=== 'number'){ 3965 valueRight = IString._fncs.getValue(rule[1], n); 3966 } else if (typeof(rule[1])=== 'object' && (IString._fncs.firstPropRule(rule[1]) === "inrange" )){ 3967 valueRight = IString._fncs.getValue(rule[1], n); 3968 } 3969 } 3970 } else { 3971 if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod 3972 valueRight = IString._fncs.getValue(rule[1], valueLeft); 3973 } else { 3974 valueRight = IString._fncs.getValue(rule[1], n); 3975 } 3976 } 3977 if(typeof(valueRight) === 'boolean') { 3978 return (valueRight ? true : false); 3979 } else { 3980 return (valueLeft == valueRight ? true :false); 3981 } 3982 }, 3983 /** 3984 * @private 3985 * @param {Object} rule 3986 * @param {number|Object} n 3987 * @return {boolean} 3988 */ 3989 neq: function(rule, n) { 3990 var valueLeft = IString._fncs.getValue(rule[0], n); 3991 var valueRight; 3992 3993 if (typeof(rule[0]) === 'string') { 3994 valueRight = n[rule[0]]; 3995 if (typeof(rule[1])=== 'number'){ 3996 valueRight = IString._fncs.getValue(rule[1], n); 3997 } 3998 } else { 3999 if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod 4000 valueRight = IString._fncs.getValue(rule[1], valueLeft); 4001 } else { 4002 valueRight = IString._fncs.getValue(rule[1], n); 4003 } 4004 } 4005 4006 if(typeof(valueRight) === 'boolean') {//mod 4007 return (valueRight? false : true); 4008 } else { 4009 return (valueLeft !== valueRight ? true :false); 4010 } 4011 4012 } 4013 }; 4014 4015 IString.prototype = { 4016 /** 4017 * Return the length of this string in characters. This function defers to the regular 4018 * Javascript string class in order to perform the length function. Please note that this 4019 * method is a real method, whereas the length property of Javascript strings is 4020 * implemented by native code and appears as a property.<p> 4021 * 4022 * Example: 4023 * 4024 * <pre> 4025 * var str = new IString("this is a string"); 4026 * console.log("String is " + str._length() + " characters long."); 4027 * </pre> 4028 * @private 4029 */ 4030 _length: function () { 4031 return this.str.length; 4032 }, 4033 4034 /** 4035 * Format this string instance as a message, replacing the parameters with 4036 * the given values.<p> 4037 * 4038 * The string can contain any text that a regular Javascript string can 4039 * contain. Replacement parameters have the syntax: 4040 * 4041 * <pre> 4042 * {name} 4043 * </pre> 4044 * 4045 * Where "name" can be any string surrounded by curly brackets. The value of 4046 * "name" is taken from the parameters argument.<p> 4047 * 4048 * Example: 4049 * 4050 * <pre> 4051 * var str = new IString("There are {num} objects."); 4052 * console.log(str.format({ 4053 * num: 12 4054 * }); 4055 * </pre> 4056 * 4057 * Would give the output: 4058 * 4059 * <pre> 4060 * There are 12 objects. 4061 * </pre> 4062 * 4063 * If a property is missing from the parameter block, the replacement 4064 * parameter substring is left untouched in the string, and a different 4065 * set of parameters may be applied a second time. This way, different 4066 * parts of the code may format different parts of the message that they 4067 * happen to know about.<p> 4068 * 4069 * Example: 4070 * 4071 * <pre> 4072 * var str = new IString("There are {num} objects in the {container}."); 4073 * console.log(str.format({ 4074 * num: 12 4075 * }); 4076 * </pre> 4077 * 4078 * Would give the output:<p> 4079 * 4080 * <pre> 4081 * There are 12 objects in the {container}. 4082 * </pre> 4083 * 4084 * The result can then be formatted again with a different parameter block that 4085 * specifies a value for the container property. 4086 * 4087 * @param params a Javascript object containing values for the replacement 4088 * parameters in the current string 4089 * @return a new IString instance with as many replacement parameters filled 4090 * out as possible with real values. 4091 */ 4092 format: function (params) { 4093 var formatted = this.str; 4094 if (params) { 4095 var regex; 4096 for (var p in params) { 4097 if (typeof(params[p]) !== 'undefined') { 4098 regex = new RegExp("\{"+p+"\}", "g"); 4099 formatted = formatted.replace(regex, params[p]); 4100 } 4101 } 4102 } 4103 return formatted.toString(); 4104 }, 4105 4106 /** 4107 * Format a string as one of a choice of strings dependent on the value of 4108 * a particular argument index.<p> 4109 * 4110 * The syntax of the choice string is as follows. The string contains a 4111 * series of choices separated by a vertical bar character "|". Each choice 4112 * has a value or range of values to match followed by a hash character "#" 4113 * followed by the string to use if the variable matches the criteria.<p> 4114 * 4115 * Example string: 4116 * 4117 * <pre> 4118 * var num = 2; 4119 * var str = new IString("0#There are no objects.|1#There is one object.|2#There are {number} objects."); 4120 * console.log(str.formatChoice(num, { 4121 * number: num 4122 * })); 4123 * </pre> 4124 * 4125 * Gives the output: 4126 * 4127 * <pre> 4128 * "There are 2 objects." 4129 * </pre> 4130 * 4131 * The strings to format may contain replacement variables that will be formatted 4132 * using the format() method above and the params argument as a source of values 4133 * to use while formatting those variables.<p> 4134 * 4135 * If the criterion for a particular choice is empty, that choice will be used 4136 * as the default one for use when none of the other choice's criteria match.<p> 4137 * 4138 * Example string: 4139 * 4140 * <pre> 4141 * var num = 22; 4142 * var str = new IString("0#There are no objects.|1#There is one object.|#There are {number} objects."); 4143 * console.log(str.formatChoice(num, { 4144 * number: num 4145 * })); 4146 * </pre> 4147 * 4148 * Gives the output: 4149 * 4150 * <pre> 4151 * "There are 22 objects." 4152 * </pre> 4153 * 4154 * If multiple choice patterns can match a given argument index, the first one 4155 * encountered in the string will be used. If no choice patterns match the 4156 * argument index, then the default choice will be used. If there is no default 4157 * choice defined, then this method will return an empty string.<p> 4158 * 4159 * <b>Special Syntax</b><p> 4160 * 4161 * For any choice format string, all of the patterns in the string should be 4162 * of a single type: numeric, boolean, or string/regexp. The type of the 4163 * patterns is determined by the type of the argument index parameter.<p> 4164 * 4165 * If the argument index is numeric, then some special syntax can be used 4166 * in the patterns to match numeric ranges.<p> 4167 * 4168 * <ul> 4169 * <li><i>>x</i> - match any number that is greater than x 4170 * <li><i>>=x</i> - match any number that is greater than or equal to x 4171 * <li><i><x</i> - match any number that is less than x 4172 * <li><i><=x</i> - match any number that is less than or equal to x 4173 * <li><i>start-end</i> - match any number in the range [start,end) 4174 * <li><i>zero</i> - match any number in the class "zero". (See below for 4175 * a description of number classes.) 4176 * <li><i>one</i> - match any number in the class "one" 4177 * <li><i>two</i> - match any number in the class "two" 4178 * <li><i>few</i> - match any number in the class "few" 4179 * <li><i>many</i> - match any number in the class "many" 4180 * </ul> 4181 * 4182 * A number class defines a set of numbers that receive a particular syntax 4183 * in the strings. For example, in Slovenian, integers ending in the digit 4184 * "1" are in the "one" class, including 1, 21, 31, ... 101, 111, etc. 4185 * Similarly, integers ending in the digit "2" are in the "two" class. 4186 * Integers ending in the digits "3" or "4" are in the "few" class, and 4187 * every other integer is handled by the default string.<p> 4188 * 4189 * The definition of what numbers are included in a class is locale-dependent. 4190 * They are defined in the data file plurals.json. If your string is in a 4191 * different locale than the default for ilib, you should call the setLocale() 4192 * method of the string instance before calling this method.<p> 4193 * 4194 * <b>Other Pattern Types</b><p> 4195 * 4196 * If the argument index is a boolean, the string values "true" and "false" 4197 * may appear as the choice patterns.<p> 4198 * 4199 * If the argument index is of type string, then the choice patterns may contain 4200 * regular expressions, or static strings as degenerate regexps. 4201 * 4202 * @param {*} argIndex The index into the choice array of the current parameter 4203 * @param {Object} params The hash of parameter values that replace the replacement 4204 * variables in the string 4205 * @throws "syntax error in choice format pattern: " if there is a syntax error 4206 * @return {string} the formatted string 4207 */ 4208 formatChoice: function(argIndex, params) { 4209 var choices = this.str.split("|"); 4210 var type = typeof(argIndex); 4211 var limits = []; 4212 var strings = []; 4213 var i; 4214 var parts; 4215 var limit; 4216 var arg; 4217 var result = undefined; 4218 var defaultCase = ""; 4219 var numberDigits = {}; 4220 var operandValue = {}; 4221 4222 if (this.str.length === 0) { 4223 // nothing to do 4224 return ""; 4225 } 4226 4227 // first parse all the choices 4228 for (i = 0; i < choices.length; i++) { 4229 parts = choices[i].split("#"); 4230 if (parts.length > 2) { 4231 limits[i] = parts[0]; 4232 parts = parts.shift(); 4233 strings[i] = parts.join("#"); 4234 } else if (parts.length === 2) { 4235 limits[i] = parts[0]; 4236 strings[i] = parts[1]; 4237 } else { 4238 // syntax error 4239 throw "syntax error in choice format pattern: " + choices[i]; 4240 } 4241 } 4242 4243 // then apply the argument index 4244 for (i = 0; i < limits.length; i++) { 4245 if (limits[i].length === 0) { 4246 // this is default case 4247 defaultCase = new IString(strings[i]); 4248 } else { 4249 switch (type) { 4250 case 'number': 4251 operandValue = IString._fncs.calculateNumberDigits(argIndex); 4252 4253 if (limits[i].substring(0,2) === "<=") { 4254 limit = parseFloat(limits[i].substring(2)); 4255 if (operandValue.n <= limit) { 4256 result = new IString(strings[i]); 4257 i = limits.length; 4258 } 4259 } else if (limits[i].substring(0,2) === ">=") { 4260 limit = parseFloat(limits[i].substring(2)); 4261 if (operandValue.n >= limit) { 4262 result = new IString(strings[i]); 4263 i = limits.length; 4264 } 4265 } else if (limits[i].charAt(0) === "<") { 4266 limit = parseFloat(limits[i].substring(1)); 4267 if (operandValue.n < limit) { 4268 result = new IString(strings[i]); 4269 i = limits.length; 4270 } 4271 } else if (limits[i].charAt(0) === ">") { 4272 limit = parseFloat(limits[i].substring(1)); 4273 if (operandValue.n > limit) { 4274 result = new IString(strings[i]); 4275 i = limits.length; 4276 } 4277 } else { 4278 this.locale = this.locale || new Locale(this.localeSpec); 4279 switch (limits[i]) { 4280 case "zero": 4281 case "one": 4282 case "two": 4283 case "few": 4284 case "many": 4285 // CLDR locale-dependent number classes 4286 var ruleset = ilib.data["plurals_" + this.locale.getLanguage()]; 4287 if (ruleset) { 4288 var rule = ruleset[limits[i]]; 4289 if (IString._fncs.getValue(rule, operandValue)) { 4290 result = new IString(strings[i]); 4291 i = limits.length; 4292 } 4293 } 4294 break; 4295 default: 4296 var dash = limits[i].indexOf("-"); 4297 if (dash !== -1) { 4298 // range 4299 var start = limits[i].substring(0, dash); 4300 var end = limits[i].substring(dash+1); 4301 if (operandValue.n >= parseInt(start, 10) && operandValue.n <= parseInt(end, 10)) { 4302 result = new IString(strings[i]); 4303 i = limits.length; 4304 } 4305 } else if (operandValue.n === parseInt(limits[i], 10)) { 4306 // exact amount 4307 result = new IString(strings[i]); 4308 i = limits.length; 4309 } 4310 break; 4311 } 4312 } 4313 break; 4314 case 'boolean': 4315 if (limits[i] === "true" && argIndex === true) { 4316 result = new IString(strings[i]); 4317 i = limits.length; 4318 } else if (limits[i] === "false" && argIndex === false) { 4319 result = new IString(strings[i]); 4320 i = limits.length; 4321 } 4322 break; 4323 case 'string': 4324 var regexp = new RegExp(limits[i], "i"); 4325 if (regexp.test(argIndex)) { 4326 result = new IString(strings[i]); 4327 i = limits.length; 4328 } 4329 break; 4330 case 'object': 4331 throw "syntax error: fmtChoice parameter for the argument index cannot be an object"; 4332 } 4333 } 4334 } 4335 4336 if (!result) { 4337 result = defaultCase || new IString(""); 4338 } 4339 4340 result = result.format(params); 4341 4342 return result.toString(); 4343 }, 4344 4345 // delegates 4346 /** 4347 * Same as String.toString() 4348 * @return {string} this instance as regular Javascript string 4349 */ 4350 toString: function () { 4351 return this.str.toString(); 4352 }, 4353 4354 /** 4355 * Same as String.valueOf() 4356 * @return {string} this instance as a regular Javascript string 4357 */ 4358 valueOf: function () { 4359 return this.str.valueOf(); 4360 }, 4361 4362 /** 4363 * Same as String.charAt() 4364 * @param {number} index the index of the character being sought 4365 * @return {IString} the character at the given index 4366 */ 4367 charAt: function(index) { 4368 return new IString(this.str.charAt(index)); 4369 }, 4370 4371 /** 4372 * Same as String.charCodeAt(). This only reports on 4373 * 2-byte UCS-2 Unicode values, and does not take into 4374 * account supplementary characters encoded in UTF-16. 4375 * If you would like to take account of those characters, 4376 * use codePointAt() instead. 4377 * @param {number} index the index of the character being sought 4378 * @return {number} the character code of the character at the 4379 * given index in the string 4380 */ 4381 charCodeAt: function(index) { 4382 return this.str.charCodeAt(index); 4383 }, 4384 4385 /** 4386 * Same as String.concat() 4387 * @param {string} strings strings to concatenate to the current one 4388 * @return {IString} a concatenation of the given strings 4389 */ 4390 concat: function(strings) { 4391 return new IString(this.str.concat(strings)); 4392 }, 4393 4394 /** 4395 * Same as String.indexOf() 4396 * @param {string} searchValue string to search for 4397 * @param {number} start index into the string to start searching, or 4398 * undefined to search the entire string 4399 * @return {number} index into the string of the string being sought, 4400 * or -1 if the string is not found 4401 */ 4402 indexOf: function(searchValue, start) { 4403 return this.str.indexOf(searchValue, start); 4404 }, 4405 4406 /** 4407 * Same as String.lastIndexOf() 4408 * @param {string} searchValue string to search for 4409 * @param {number} start index into the string to start searching, or 4410 * undefined to search the entire string 4411 * @return {number} index into the string of the string being sought, 4412 * or -1 if the string is not found 4413 */ 4414 lastIndexOf: function(searchValue, start) { 4415 return this.str.lastIndexOf(searchValue, start); 4416 }, 4417 4418 /** 4419 * Same as String.match() 4420 * @param {string} regexp the regular expression to match 4421 * @return {Array.<string>} an array of matches 4422 */ 4423 match: function(regexp) { 4424 return this.str.match(regexp); 4425 }, 4426 4427 /** 4428 * Same as String.replace() 4429 * @param {string} searchValue a regular expression to search for 4430 * @param {string} newValue the string to replace the matches with 4431 * @return {IString} a new string with all the matches replaced 4432 * with the new value 4433 */ 4434 replace: function(searchValue, newValue) { 4435 return new IString(this.str.replace(searchValue, newValue)); 4436 }, 4437 4438 /** 4439 * Same as String.search() 4440 * @param {string} regexp the regular expression to search for 4441 * @return {number} position of the match, or -1 for no match 4442 */ 4443 search: function(regexp) { 4444 return this.str.search(regexp); 4445 }, 4446 4447 /** 4448 * Same as String.slice() 4449 * @param {number} start first character to include in the string 4450 * @param {number} end include all characters up to, but not including 4451 * the end character 4452 * @return {IString} a slice of the current string 4453 */ 4454 slice: function(start, end) { 4455 return new IString(this.str.slice(start, end)); 4456 }, 4457 4458 /** 4459 * Same as String.split() 4460 * @param {string} separator regular expression to match to find 4461 * separations between the parts of the text 4462 * @param {number} limit maximum number of items in the final 4463 * output array. Any items beyond that limit will be ignored. 4464 * @return {Array.<string>} the parts of the current string split 4465 * by the separator 4466 */ 4467 split: function(separator, limit) { 4468 return this.str.split(separator, limit); 4469 }, 4470 4471 /** 4472 * Same as String.substr() 4473 * @param {number} start the index of the character that should 4474 * begin the returned substring 4475 * @param {number} length the number of characters to return after 4476 * the start character. 4477 * @return {IString} the requested substring 4478 */ 4479 substr: function(start, length) { 4480 var plat = ilib._getPlatform(); 4481 if (plat === "qt" || plat === "rhino" || plat === "trireme") { 4482 // qt and rhino have a broken implementation of substr(), so 4483 // work around it 4484 if (typeof(length) === "undefined") { 4485 length = this.str.length - start; 4486 } 4487 } 4488 return new IString(this.str.substr(start, length)); 4489 }, 4490 4491 /** 4492 * Same as String.substring() 4493 * @param {number} from the index of the character that should 4494 * begin the returned substring 4495 * @param {number} to the index where to stop the extraction. If 4496 * omitted, extracts the rest of the string 4497 * @return {IString} the requested substring 4498 */ 4499 substring: function(from, to) { 4500 return this.str.substring(from, to); 4501 }, 4502 4503 /** 4504 * Same as String.toLowerCase(). Note that this method is 4505 * not locale-sensitive. 4506 * @return {IString} a string with the first character 4507 * lower-cased 4508 */ 4509 toLowerCase: function() { 4510 return this.str.toLowerCase(); 4511 }, 4512 4513 /** 4514 * Same as String.toUpperCase(). Note that this method is 4515 * not locale-sensitive. Use toLocaleUpperCase() instead 4516 * to get locale-sensitive behaviour. 4517 * @return {IString} a string with the first character 4518 * upper-cased 4519 */ 4520 toUpperCase: function() { 4521 return this.str.toUpperCase(); 4522 }, 4523 4524 /** 4525 * Convert the character or the surrogate pair at the given 4526 * index into the string to a Unicode UCS-4 code point. 4527 * @protected 4528 * @param {number} index index into the string 4529 * @return {number} code point of the character at the 4530 * given index into the string 4531 */ 4532 _toCodePoint: function (index) { 4533 return IString.toCodePoint(this.str, index); 4534 }, 4535 4536 /** 4537 * Call the callback with each character in the string one at 4538 * a time, taking care to step through the surrogate pairs in 4539 * the UTF-16 encoding properly.<p> 4540 * 4541 * The standard Javascript String's charAt() method only 4542 * returns a particular 16-bit character in the 4543 * UTF-16 encoding scheme. 4544 * If the index to charAt() is pointing to a low- or 4545 * high-surrogate character, 4546 * it will return the surrogate character rather 4547 * than the the character 4548 * in the supplementary planes that the two surrogates together 4549 * encode. This function will call the callback with the full 4550 * character, making sure to join two 4551 * surrogates into one character in the supplementary planes 4552 * where necessary.<p> 4553 * 4554 * @param {function(string)} callback a callback function to call with each 4555 * full character in the current string 4556 */ 4557 forEach: function(callback) { 4558 if (typeof(callback) === 'function') { 4559 var it = this.charIterator(); 4560 while (it.hasNext()) { 4561 callback(it.next()); 4562 } 4563 } 4564 }, 4565 4566 /** 4567 * Call the callback with each numeric code point in the string one at 4568 * a time, taking care to step through the surrogate pairs in 4569 * the UTF-16 encoding properly.<p> 4570 * 4571 * The standard Javascript String's charCodeAt() method only 4572 * returns information about a particular 16-bit character in the 4573 * UTF-16 encoding scheme. 4574 * If the index to charCodeAt() is pointing to a low- or 4575 * high-surrogate character, 4576 * it will return the code point of the surrogate character rather 4577 * than the code point of the character 4578 * in the supplementary planes that the two surrogates together 4579 * encode. This function will call the callback with the full 4580 * code point of each character, making sure to join two 4581 * surrogates into one code point in the supplementary planes.<p> 4582 * 4583 * @param {function(string)} callback a callback function to call with each 4584 * code point in the current string 4585 */ 4586 forEachCodePoint: function(callback) { 4587 if (typeof(callback) === 'function') { 4588 var it = this.iterator(); 4589 while (it.hasNext()) { 4590 callback(it.next()); 4591 } 4592 } 4593 }, 4594 4595 /** 4596 * Return an iterator that will step through all of the characters 4597 * in the string one at a time and return their code points, taking 4598 * care to step through the surrogate pairs in UTF-16 encoding 4599 * properly.<p> 4600 * 4601 * The standard Javascript String's charCodeAt() method only 4602 * returns information about a particular 16-bit character in the 4603 * UTF-16 encoding scheme. 4604 * If the index is pointing to a low- or high-surrogate character, 4605 * it will return a code point of the surrogate character rather 4606 * than the code point of the character 4607 * in the supplementary planes that the two surrogates together 4608 * encode.<p> 4609 * 4610 * The iterator instance returned has two methods, hasNext() which 4611 * returns true if the iterator has more code points to iterate through, 4612 * and next() which returns the next code point as a number.<p> 4613 * 4614 * @return {Object} an iterator 4615 * that iterates through all the code points in the string 4616 */ 4617 iterator: function() { 4618 /** 4619 * @constructor 4620 */ 4621 function _iterator (istring) { 4622 this.index = 0; 4623 this.hasNext = function () { 4624 return (this.index < istring.str.length); 4625 }; 4626 this.next = function () { 4627 if (this.index < istring.str.length) { 4628 var num = istring._toCodePoint(this.index); 4629 this.index += ((num > 0xFFFF) ? 2 : 1); 4630 } else { 4631 num = -1; 4632 } 4633 return num; 4634 }; 4635 }; 4636 return new _iterator(this); 4637 }, 4638 4639 /** 4640 * Return an iterator that will step through all of the characters 4641 * in the string one at a time, taking 4642 * care to step through the surrogate pairs in UTF-16 encoding 4643 * properly.<p> 4644 * 4645 * The standard Javascript String's charAt() method only 4646 * returns information about a particular 16-bit character in the 4647 * UTF-16 encoding scheme. 4648 * If the index is pointing to a low- or high-surrogate character, 4649 * it will return that surrogate character rather 4650 * than the surrogate pair which represents a character 4651 * in the supplementary planes.<p> 4652 * 4653 * The iterator instance returned has two methods, hasNext() which 4654 * returns true if the iterator has more characters to iterate through, 4655 * and next() which returns the next character.<p> 4656 * 4657 * @return {Object} an iterator 4658 * that iterates through all the characters in the string 4659 */ 4660 charIterator: function() { 4661 /** 4662 * @constructor 4663 */ 4664 function _chiterator (istring) { 4665 this.index = 0; 4666 this.hasNext = function () { 4667 return (this.index < istring.str.length); 4668 }; 4669 this.next = function () { 4670 var ch; 4671 if (this.index < istring.str.length) { 4672 ch = istring.str.charAt(this.index); 4673 if (IString._isSurrogate(ch) && 4674 this.index+1 < istring.str.length && 4675 IString._isSurrogate(istring.str.charAt(this.index+1))) { 4676 this.index++; 4677 ch += istring.str.charAt(this.index); 4678 } 4679 this.index++; 4680 } 4681 return ch; 4682 }; 4683 }; 4684 return new _chiterator(this); 4685 }, 4686 4687 /** 4688 * Return the code point at the given index when the string is viewed 4689 * as an array of code points. If the index is beyond the end of the 4690 * array of code points or if the index is negative, -1 is returned. 4691 * @param {number} index index of the code point 4692 * @return {number} code point of the character at the given index into 4693 * the string 4694 */ 4695 codePointAt: function (index) { 4696 if (index < 0) { 4697 return -1; 4698 } 4699 var count, 4700 it = this.iterator(), 4701 ch; 4702 for (count = index; count >= 0 && it.hasNext(); count--) { 4703 ch = it.next(); 4704 } 4705 return (count < 0) ? ch : -1; 4706 }, 4707 4708 /** 4709 * Set the locale to use when processing choice formats. The locale 4710 * affects how number classes are interpretted. In some cultures, 4711 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 4712 * in yet others, "few" maps to "any integer that ends in the digits 4713 * 3 or 4". 4714 * @param {Locale|string} locale locale to use when processing choice 4715 * formats with this string 4716 * @param {boolean=} sync [optional] whether to load the locale data synchronously 4717 * or not 4718 * @param {Object=} loadParams [optional] parameters to pass to the loader function 4719 * @param {function(*)=} onLoad [optional] function to call when the loading is done 4720 */ 4721 setLocale: function (locale, sync, loadParams, onLoad) { 4722 if (typeof(locale) === 'object') { 4723 this.locale = locale; 4724 } else { 4725 this.localeSpec = locale; 4726 this.locale = new Locale(locale); 4727 } 4728 4729 IString.loadPlurals(typeof(sync) !== 'undefined' ? sync : true, this.locale, loadParams, onLoad); 4730 }, 4731 4732 /** 4733 * Return the locale to use when processing choice formats. The locale 4734 * affects how number classes are interpretted. In some cultures, 4735 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 4736 * in yet others, "few" maps to "any integer that ends in the digits 4737 * 3 or 4". 4738 * @return {string} localespec to use when processing choice 4739 * formats with this string 4740 */ 4741 getLocale: function () { 4742 return (this.locale ? this.locale.getSpec() : this.localeSpec) || ilib.getLocale(); 4743 }, 4744 4745 /** 4746 * Return the number of code points in this string. This may be different 4747 * than the number of characters, as the UTF-16 encoding that Javascript 4748 * uses for its basis returns surrogate pairs separately. Two 2-byte 4749 * surrogate characters together make up one character/code point in 4750 * the supplementary character planes. If your string contains no 4751 * characters in the supplementary planes, this method will return the 4752 * same thing as the length() method. 4753 * @return {number} the number of code points in this string 4754 */ 4755 codePointLength: function () { 4756 if (this.cpLength === -1) { 4757 var it = this.iterator(); 4758 this.cpLength = 0; 4759 while (it.hasNext()) { 4760 this.cpLength++; 4761 it.next(); 4762 }; 4763 } 4764 return this.cpLength; 4765 } 4766 }; 4767 4768 4769 /*< Calendar.js */ 4770 /* 4771 * Calendar.js - Represent a calendar object. 4772 * 4773 * Copyright © 2012-2015, JEDLSoft 4774 * 4775 * Licensed under the Apache License, Version 2.0 (the "License"); 4776 * you may not use this file except in compliance with the License. 4777 * You may obtain a copy of the License at 4778 * 4779 * http://www.apache.org/licenses/LICENSE-2.0 4780 * 4781 * Unless required by applicable law or agreed to in writing, software 4782 * distributed under the License is distributed on an "AS IS" BASIS, 4783 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4784 * 4785 * See the License for the specific language governing permissions and 4786 * limitations under the License. 4787 */ 4788 4789 /** 4790 * @class 4791 * Superclass for all calendar subclasses that contains shared 4792 * functionality. This class is never instantiated on its own. Instead, 4793 * you should use the {@link CalendarFactory} function to manufacture a new 4794 * instance of a subclass of Calendar. 4795 * 4796 * @private 4797 * @constructor 4798 */ 4799 var Calendar = function() { 4800 }; 4801 4802 /* place for the subclasses to put their constructors so that the factory method 4803 * can find them. Do this to add your calendar after it's defined: 4804 * Calendar._constructors["mytype"] = Calendar.MyTypeConstructor; 4805 */ 4806 Calendar._constructors = {}; 4807 4808 Calendar.prototype = { 4809 /** 4810 * Return the type of this calendar. 4811 * 4812 * @return {string} the name of the type of this calendar 4813 */ 4814 getType: function() { 4815 throw "Cannot call methods of abstract class Calendar"; 4816 }, 4817 4818 /** 4819 * Return the number of months in the given year. The number of months in a year varies 4820 * for some luni-solar calendars because in some years, an extra month is needed to extend the 4821 * days in a year to an entire solar year. The month is represented as a 1-based number 4822 * where 1=first month, 2=second month, etc. 4823 * 4824 * @param {number} year a year for which the number of months is sought 4825 * @return {number} The number of months in the given year 4826 */ 4827 getNumMonths: function(year) { 4828 throw "Cannot call methods of abstract class Calendar"; 4829 }, 4830 4831 /** 4832 * Return the number of days in a particular month in a particular year. This function 4833 * can return a different number for a month depending on the year because of things 4834 * like leap years. 4835 * 4836 * @param {number} month the month for which the length is sought 4837 * @param {number} year the year within which that month can be found 4838 * @return {number} the number of days within the given month in the given year 4839 */ 4840 getMonLength: function(month, year) { 4841 throw "Cannot call methods of abstract class Calendar"; 4842 }, 4843 4844 /** 4845 * Return true if the given year is a leap year in this calendar. 4846 * The year parameter may be given as a number. 4847 * 4848 * @param {number} year the year for which the leap year information is being sought 4849 * @return {boolean} true if the given year is a leap year 4850 */ 4851 isLeapYear: function(year) { 4852 throw "Cannot call methods of abstract class Calendar"; 4853 } 4854 }; 4855 4856 4857 /*< CalendarFactory.js */ 4858 /* 4859 * CalendarFactory.js - Constructs new instances of the right subclass of Calendar 4860 * 4861 * Copyright © 2015, JEDLSoft 4862 * 4863 * Licensed under the Apache License, Version 2.0 (the "License"); 4864 * you may not use this file except in compliance with the License. 4865 * You may obtain a copy of the License at 4866 * 4867 * http://www.apache.org/licenses/LICENSE-2.0 4868 * 4869 * Unless required by applicable law or agreed to in writing, software 4870 * distributed under the License is distributed on an "AS IS" BASIS, 4871 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4872 * 4873 * See the License for the specific language governing permissions and 4874 * limitations under the License. 4875 */ 4876 4877 /* !depends 4878 ilib.js 4879 Locale.js 4880 LocaleInfo.js 4881 Calendar.js 4882 */ 4883 4884 4885 /** 4886 * Factory method to create a new instance of a calendar subclass.<p> 4887 * 4888 * The options parameter can be an object that contains the following 4889 * properties: 4890 * 4891 * <ul> 4892 * <li><i>type</i> - specify the type of the calendar desired. The 4893 * list of valid values changes depending on which calendars are 4894 * defined. When assembling your iliball.js, include those calendars 4895 * you wish to use in your program or web page, and they will register 4896 * themselves with this factory method. The "official", "gregorian", 4897 * and "julian" calendars are all included by default, as they are the 4898 * standard calendars for much of the world. 4899 * <li><i>locale</i> - some calendars vary depending on the locale. 4900 * For example, the "official" calendar transitions from a Julian-style 4901 * calendar to a Gregorian-style calendar on a different date for 4902 * each country, as the governments of those countries decided to 4903 * adopt the Gregorian calendar at different times. 4904 * 4905 * <li><i>onLoad</i> - a callback function to call when the calendar object is fully 4906 * loaded. When the onLoad option is given, the calendar factory will attempt to 4907 * load any missing locale data using the ilib loader callback. 4908 * When the constructor is done (even if the data is already preassembled), the 4909 * onLoad function is called with the current instance as a parameter, so this 4910 * callback can be used with preassembled or dynamic loading or a mix of the two. 4911 * 4912 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 4913 * asynchronously. If this option is given as "false", then the "onLoad" 4914 * callback must be given, as the instance returned from this constructor will 4915 * not be usable for a while. 4916 * 4917 * <li><i>loadParams</i> - an object containing parameters to pass to the 4918 * loader callback function when locale data is missing. The parameters are not 4919 * interpretted or modified in any way. They are simply passed along. The object 4920 * may contain any property/value pairs as long as the calling code is in 4921 * agreement with the loader callback function as to what those parameters mean. 4922 * </ul> 4923 * 4924 * If a locale is specified, but no type, then the calendar that is default for 4925 * the locale will be instantiated and returned. If neither the type nor 4926 * the locale are specified, then the calendar for the default locale will 4927 * be used. 4928 * 4929 * @static 4930 * @param {Object=} options options controlling the construction of this instance, or 4931 * undefined to use the default options 4932 * @return {Calendar} an instance of a calendar object of the appropriate type 4933 */ 4934 var CalendarFactory = function (options) { 4935 var locale, 4936 type, 4937 sync = true, 4938 instance; 4939 4940 if (options) { 4941 if (options.locale) { 4942 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 4943 } 4944 4945 type = options.type || options.calendar; 4946 4947 if (typeof(options.sync) === 'boolean') { 4948 sync = options.sync; 4949 } 4950 } 4951 4952 if (!locale) { 4953 locale = new Locale(); // default locale 4954 } 4955 4956 if (!type) { 4957 new LocaleInfo(locale, { 4958 sync: sync, 4959 loadParams: options && options.loadParams, 4960 onLoad: ilib.bind(this, function(info) { 4961 type = info.getCalendar(); 4962 4963 instance = CalendarFactory._init(type, options); 4964 4965 if (options && typeof(options.onLoad) === 'function') { 4966 options.onLoad(instance); 4967 } 4968 }) 4969 }); 4970 } else { 4971 instance = CalendarFactory._init(type, options); 4972 } 4973 4974 return instance; 4975 }; 4976 4977 /** 4978 * Map calendar names to classes to initialize in the dynamic code model. 4979 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 4980 * @private 4981 */ 4982 CalendarFactory._dynMap = { 4983 "coptic": "Coptic", 4984 "ethiopic": "Ethiopic", 4985 "gregorian": "Gregorian", 4986 "han": "Han", 4987 "hebrew": "Hebrew", 4988 "islamic": "Islamic", 4989 "julian": "Julian", 4990 "persian": "Persian", 4991 "persian-algo": "PersianAlgo", 4992 "thaisolar": "ThaiSolar" 4993 }; 4994 4995 /** 4996 * Dynamically load the code for a calendar and calendar class if necessary. 4997 * @protected 4998 */ 4999 CalendarFactory._dynLoadCalendar = function (name) { 5000 if (!Calendar._constructors[name]) { 5001 var entry = CalendarFactory._dynMap[name]; 5002 if (entry) { 5003 Calendar._constructors[name] = require("./" + entry + "Cal.js"); 5004 } 5005 } 5006 return Calendar._constructors[name]; 5007 }; 5008 5009 /** @private */ 5010 CalendarFactory._init = function(type, options) { 5011 var cons; 5012 5013 if (ilib.isDynCode()) { 5014 CalendarFactory._dynLoadCalendar(type); 5015 } 5016 5017 cons = Calendar._constructors[type]; 5018 5019 // pass the same options through to the constructor so the subclass 5020 // has the ability to do something with if it needs to 5021 return cons && new cons(options); 5022 }; 5023 5024 /** 5025 * Return an array of known calendar types that the factory method can instantiate. 5026 * 5027 * @return {Array.<string>} an array of calendar types 5028 */ 5029 CalendarFactory.getCalendars = function () { 5030 var arr = [], 5031 c; 5032 5033 if (ilib.isDynCode()) { 5034 for (c in CalendarFactory._dynMap) { 5035 CalendarFactory._dynLoadCalendar(c); 5036 } 5037 } 5038 5039 for (c in Calendar._constructors) { 5040 if (c && Calendar._constructors[c]) { 5041 arr.push(c); // code like a pirate 5042 } 5043 } 5044 5045 return arr; 5046 }; 5047 5048 5049 /*< GregorianCal.js */ 5050 /* 5051 * gregorian.js - Represent a Gregorian calendar object. 5052 * 5053 * Copyright © 2012-2015, JEDLSoft 5054 * 5055 * Licensed under the Apache License, Version 2.0 (the "License"); 5056 * you may not use this file except in compliance with the License. 5057 * You may obtain a copy of the License at 5058 * 5059 * http://www.apache.org/licenses/LICENSE-2.0 5060 * 5061 * Unless required by applicable law or agreed to in writing, software 5062 * distributed under the License is distributed on an "AS IS" BASIS, 5063 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5064 * 5065 * See the License for the specific language governing permissions and 5066 * limitations under the License. 5067 */ 5068 5069 5070 /* !depends ilib.js Calendar.js Utils.js MathUtils.js */ 5071 5072 5073 /** 5074 * @class 5075 * Construct a new Gregorian calendar object. This class encodes information about 5076 * a Gregorian calendar.<p> 5077 * 5078 * 5079 * @constructor 5080 * @param {{noinstance:boolean}=} options 5081 * @extends Calendar 5082 */ 5083 var GregorianCal = function(options) { 5084 if (!options || !options.noinstance) { 5085 this.type = "gregorian"; 5086 } 5087 }; 5088 5089 /** 5090 * the lengths of each month 5091 * @private 5092 * @const 5093 * @type Array.<number> 5094 */ 5095 GregorianCal.monthLengths = [ 5096 31, /* Jan */ 5097 28, /* Feb */ 5098 31, /* Mar */ 5099 30, /* Apr */ 5100 31, /* May */ 5101 30, /* Jun */ 5102 31, /* Jul */ 5103 31, /* Aug */ 5104 30, /* Sep */ 5105 31, /* Oct */ 5106 30, /* Nov */ 5107 31 /* Dec */ 5108 ]; 5109 5110 /** 5111 * Return the number of months in the given year. The number of months in a year varies 5112 * for some luni-solar calendars because in some years, an extra month is needed to extend the 5113 * days in a year to an entire solar year. The month is represented as a 1-based number 5114 * where 1=first month, 2=second month, etc. 5115 * 5116 * @param {number} year a year for which the number of months is sought 5117 * @return {number} The number of months in the given year 5118 */ 5119 GregorianCal.prototype.getNumMonths = function(year) { 5120 return 12; 5121 }; 5122 5123 /** 5124 * Return the number of days in a particular month in a particular year. This function 5125 * can return a different number for a month depending on the year because of things 5126 * like leap years. 5127 * 5128 * @param {number} month the month for which the length is sought 5129 * @param {number} year the year within which that month can be found 5130 * @return {number} the number of days within the given month in the given year 5131 */ 5132 GregorianCal.prototype.getMonLength = function(month, year) { 5133 if (month !== 2 || !this.isLeapYear(year)) { 5134 return GregorianCal.monthLengths[month-1]; 5135 } else { 5136 return 29; 5137 } 5138 }; 5139 5140 /** 5141 * Return true if the given year is a leap year in the Gregorian calendar. 5142 * The year parameter may be given as a number, or as a GregDate object. 5143 * @param {number|GregorianDate} year the year for which the leap year information is being sought 5144 * @return {boolean} true if the given year is a leap year 5145 */ 5146 GregorianCal.prototype.isLeapYear = function(year) { 5147 var y = (typeof(year) === 'number' ? year : year.getYears()); 5148 var centuries = MathUtils.mod(y, 400); 5149 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 5150 }; 5151 5152 /** 5153 * Return the type of this calendar. 5154 * 5155 * @return {string} the name of the type of this calendar 5156 */ 5157 GregorianCal.prototype.getType = function() { 5158 return this.type; 5159 }; 5160 5161 /** 5162 * Return a date instance for this calendar type using the given 5163 * options. 5164 * @param {Object} options options controlling the construction of 5165 * the date instance 5166 * @return {IDate} a date appropriate for this calendar type 5167 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 5168 */ 5169 GregorianCal.prototype.newDateInstance = function (options) { 5170 return new GregorianDate(options); 5171 }; 5172 5173 /* register this calendar for the factory method */ 5174 Calendar._constructors["gregorian"] = GregorianCal; 5175 5176 5177 /*< JulianDay.js */ 5178 /* 5179 * JulianDay.js - A Julian Day object. 5180 * 5181 * Copyright © 2012-2015, JEDLSoft 5182 * 5183 * Licensed under the Apache License, Version 2.0 (the "License"); 5184 * you may not use this file except in compliance with the License. 5185 * You may obtain a copy of the License at 5186 * 5187 * http://www.apache.org/licenses/LICENSE-2.0 5188 * 5189 * Unless required by applicable law or agreed to in writing, software 5190 * distributed under the License is distributed on an "AS IS" BASIS, 5191 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5192 * 5193 * See the License for the specific language governing permissions and 5194 * limitations under the License. 5195 */ 5196 5197 /** 5198 * @class 5199 * A Julian Day class. A Julian Day is a date based on the Julian Day count 5200 * of time invented by Joseph Scaliger in 1583 for use with astronomical calculations. 5201 * Do not confuse it with a date in the Julian calendar, which it has very 5202 * little in common with. The naming is unfortunately close, and comes from history.<p> 5203 * 5204 * 5205 * @constructor 5206 * @param {number} num the Julian Day expressed as a floating point number 5207 */ 5208 var JulianDay = function(num) { 5209 this.jd = num; 5210 this.days = Math.floor(this.jd); 5211 this.frac = num - this.days; 5212 }; 5213 5214 JulianDay.prototype = { 5215 /** 5216 * Return the integral portion of this Julian Day instance. This corresponds to 5217 * the number of days since the beginning of the epoch. 5218 * 5219 * @return {number} the integral portion of this Julian Day 5220 */ 5221 getDays: function() { 5222 return this.days; 5223 }, 5224 5225 /** 5226 * Set the date of this Julian Day instance. 5227 * 5228 * @param {number} days the julian date expressed as a floating point number 5229 */ 5230 setDays: function(days) { 5231 this.days = Math.floor(days); 5232 this.jd = this.days + this.frac; 5233 }, 5234 5235 /** 5236 * Return the fractional portion of this Julian Day instance. This portion 5237 * corresponds to the time of day for the instance. 5238 */ 5239 getDayFraction: function() { 5240 return this.frac; 5241 }, 5242 5243 /** 5244 * Set the fractional part of the Julian Day. The fractional part represents 5245 * the portion of a fully day. Julian dates start at noon, and proceed until 5246 * noon of the next day. That would mean midnight is represented as a fractional 5247 * part of 0.5. 5248 * 5249 * @param {number} fraction The fractional part of the Julian date 5250 */ 5251 setDayFraction: function(fraction) { 5252 var t = Math.floor(fraction); 5253 this.frac = fraction - t; 5254 this.jd = this.days + this.frac; 5255 }, 5256 5257 /** 5258 * Return the Julian Day expressed as a floating point number. 5259 * @return {number} the Julian Day as a number 5260 */ 5261 getDate: function () { 5262 return this.jd; 5263 }, 5264 5265 /** 5266 * Set the date of this Julian Day instance. 5267 * 5268 * @param {number} num the numeric Julian Day to set into this instance 5269 */ 5270 setDate: function (num) { 5271 this.jd = num; 5272 }, 5273 5274 /** 5275 * Add an offset to the current date instance. The offset should be expressed in 5276 * terms of Julian days. That is, each integral unit represents one day of time, and 5277 * fractional part represents a fraction of a regular 24-hour day. 5278 * 5279 * @param {number} offset an amount to add (or subtract) to the current result instance. 5280 */ 5281 addDate: function(offset) { 5282 if (typeof(offset) === 'number') { 5283 this.jd += offset; 5284 this.days = Math.floor(this.jd); 5285 this.frac = this.jd - this.days; 5286 } 5287 } 5288 }; 5289 5290 5291 5292 /*< RataDie.js */ 5293 /* 5294 * ratadie.js - Represent the RD date number in the calendar 5295 * 5296 * Copyright © 2014-2015, JEDLSoft 5297 * 5298 * Licensed under the Apache License, Version 2.0 (the "License"); 5299 * you may not use this file except in compliance with the License. 5300 * You may obtain a copy of the License at 5301 * 5302 * http://www.apache.org/licenses/LICENSE-2.0 5303 * 5304 * Unless required by applicable law or agreed to in writing, software 5305 * distributed under the License is distributed on an "AS IS" BASIS, 5306 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5307 * 5308 * See the License for the specific language governing permissions and 5309 * limitations under the License. 5310 */ 5311 5312 /* !depends 5313 ilib.js 5314 JulianDay.js 5315 MathUtils.js 5316 JSUtils.js 5317 */ 5318 5319 5320 /** 5321 * @class 5322 * Construct a new RD date number object. The constructor parameters can 5323 * contain any of the following properties: 5324 * 5325 * <ul> 5326 * <li><i>unixtime<i> - sets the time of this instance according to the given 5327 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 5328 * 5329 * <li><i>julianday</i> - sets the time of this instance according to the given 5330 * Julian Day instance or the Julian Day given as a float 5331 * 5332 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 5333 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 5334 * linear count of years since the beginning of the epoch, much like other calendars. This linear 5335 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 5336 * to 60 and treated as if it were a year in the regular 60-year cycle. 5337 * 5338 * <li><i>year</i> - any integer, including 0 5339 * 5340 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 5341 * 5342 * <li><i>day</i> - 1 to 31 5343 * 5344 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 5345 * is always done with an unambiguous 24 hour representation 5346 * 5347 * <li><i>minute</i> - 0 to 59 5348 * 5349 * <li><i>second</i> - 0 to 59 5350 * 5351 * <li><i>millisecond</i> - 0 to 999 5352 * 5353 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 5354 * the parts or specify the minutes, seconds, and milliseconds, but not both. This is only used 5355 * in the Hebrew calendar. 5356 * 5357 * <li><i>minute</i> - 0 to 59 5358 * 5359 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 5360 * </ul> 5361 * 5362 * If the constructor is called with another date instance instead of 5363 * a parameter block, the other instance acts as a parameter block and its 5364 * settings are copied into the current instance.<p> 5365 * 5366 * If the constructor is called with no arguments at all or if none of the 5367 * properties listed above are present, then the RD is calculate based on 5368 * the current date at the time of instantiation. <p> 5369 * 5370 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 5371 * specified in the params, it is assumed that they have the smallest possible 5372 * value in the range for the property (zero or one).<p> 5373 * 5374 * 5375 * @private 5376 * @constructor 5377 * @param {Object=} params parameters that govern the settings and behaviour of this RD date 5378 */ 5379 var RataDie = function(params) { 5380 if (params) { 5381 if (typeof(params.date) !== 'undefined') { 5382 // accept JS Date classes or strings 5383 var date = params.date; 5384 if (!(JSUtils.isDate(date))) { 5385 date = new Date(date); // maybe a string initializer? 5386 } 5387 this._setTime(date.getTime()); 5388 } else if (typeof(params.unixtime) !== 'undefined') { 5389 this._setTime(parseInt(params.unixtime, 10)); 5390 } else if (typeof(params.julianday) !== 'undefined') { 5391 // JD time is defined to be UTC 5392 this._setJulianDay(parseFloat(params.julianday)); 5393 } else if (params.year || params.month || params.day || params.hour || 5394 params.minute || params.second || params.millisecond || params.parts || params.cycle) { 5395 this._setDateComponents(params); 5396 } else if (typeof(params.rd) !== 'undefined') { 5397 /** 5398 * @type {number} the Rata Die number of this date for this calendar type 5399 */ 5400 this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; 5401 } 5402 } 5403 5404 if (typeof(this.rd) === 'undefined' || isNaN(this.rd)) { 5405 var now = new Date(); 5406 this._setTime(now.getTime()); 5407 } 5408 }; 5409 5410 /** 5411 * @private 5412 * @const 5413 * @type {number} 5414 */ 5415 RataDie.gregorianEpoch = 1721424.5; 5416 5417 RataDie.prototype = { 5418 /** 5419 * @protected 5420 * @type {number} 5421 * the difference between a zero Julian day and the zero Gregorian date. 5422 */ 5423 epoch: RataDie.gregorianEpoch, 5424 5425 /** 5426 * Set the RD of this instance according to the given unix time. Unix time is 5427 * the number of milliseconds since midnight on Jan 1, 1970. 5428 * 5429 * @protected 5430 * @param {number} millis the unix time to set this date to in milliseconds 5431 */ 5432 _setTime: function(millis) { 5433 // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) 5434 this._setJulianDay(2440587.5 + millis / 86400000); 5435 }, 5436 5437 /** 5438 * Set the date of this instance using a Julian Day. 5439 * @protected 5440 * @param {number} date the Julian Day to use to set this date 5441 */ 5442 _setJulianDay: function (date) { 5443 var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; 5444 // round to the nearest millisecond 5445 this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; 5446 }, 5447 5448 /** 5449 * Return the rd number of the particular day of the week on or before the 5450 * given rd. eg. The Sunday on or before the given rd. 5451 * @protected 5452 * @param {number} rd the rata die date of the reference date 5453 * @param {number} dayOfWeek the day of the week that is being sought relative 5454 * to the current date 5455 * @return {number} the rd of the day of the week 5456 */ 5457 _onOrBefore: function(rd, dayOfWeek) { 5458 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); 5459 }, 5460 5461 /** 5462 * Return the rd number of the particular day of the week on or before the current rd. 5463 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 5464 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5465 * wall time, so it it would give the wrong day of the week if this calculation was 5466 * done in UTC time when the caller really wanted wall time. Even though the calculation 5467 * may be done in wall time, the return value is nonetheless always given in UTC. 5468 * @param {number} dayOfWeek the day of the week that is being sought relative 5469 * to the current date 5470 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5471 * not given 5472 * @return {number} the rd of the day of the week 5473 */ 5474 onOrBefore: function(dayOfWeek, offset) { 5475 offset = offset || 0; 5476 return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; 5477 }, 5478 5479 /** 5480 * Return the rd number of the particular day of the week on or before the current rd. 5481 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 5482 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5483 * wall time, so it it would give the wrong day of the week if this calculation was 5484 * done in UTC time when the caller really wanted wall time. Even though the calculation 5485 * may be done in wall time, the return value is nonetheless always given in UTC. 5486 * @param {number} dayOfWeek the day of the week that is being sought relative 5487 * to the reference date 5488 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5489 * not given 5490 * @return {number} the day of the week 5491 */ 5492 onOrAfter: function(dayOfWeek, offset) { 5493 offset = offset || 0; 5494 return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; 5495 }, 5496 5497 /** 5498 * Return the rd number of the particular day of the week before the current rd. 5499 * eg. The Sunday before the current rd. If the offset is given, the calculation 5500 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5501 * wall time, so it it would give the wrong day of the week if this calculation was 5502 * done in UTC time when the caller really wanted wall time. Even though the calculation 5503 * may be done in wall time, the return value is nonetheless always given in UTC. 5504 * @param {number} dayOfWeek the day of the week that is being sought relative 5505 * to the reference date 5506 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5507 * not given 5508 * @return {number} the day of the week 5509 */ 5510 before: function(dayOfWeek, offset) { 5511 offset = offset || 0; 5512 return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; 5513 }, 5514 5515 /** 5516 * Return the rd number of the particular day of the week after the current rd. 5517 * eg. The Sunday after the current rd. If the offset is given, the calculation 5518 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5519 * wall time, so it it would give the wrong day of the week if this calculation was 5520 * done in UTC time when the caller really wanted wall time. Even though the calculation 5521 * may be done in wall time, the return value is nonetheless always given in UTC. 5522 * @param {number} dayOfWeek the day of the week that is being sought relative 5523 * to the reference date 5524 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5525 * not given 5526 * @return {number} the day of the week 5527 */ 5528 after: function(dayOfWeek, offset) { 5529 offset = offset || 0; 5530 return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; 5531 }, 5532 5533 /** 5534 * Return the unix time equivalent to this Gregorian date instance. Unix time is 5535 * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only 5536 * returns a valid number for dates between midnight, Jan 1, 1970 and 5537 * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance 5538 * encodes a date outside of that range, this method will return -1. 5539 * 5540 * @return {number} a number giving the unix time, or -1 if the date is outside the 5541 * valid unix time range 5542 */ 5543 getTime: function() { 5544 // earlier than Jan 1, 1970 5545 // or later than Jan 19, 2038 at 3:14:07am 5546 var jd = this.getJulianDay(); 5547 if (jd < 2440587.5 || jd > 2465442.634803241) { 5548 return -1; 5549 } 5550 5551 // avoid the rounding errors in the floating point math by only using 5552 // the whole days from the rd, and then calculating the milliseconds directly 5553 return Math.round((jd - 2440587.5) * 86400000); 5554 }, 5555 5556 /** 5557 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 5558 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 5559 * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus 5560 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 5561 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 5562 * after Jan 1, 1970, and even more interestingly 100 million days worth of time before 5563 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 5564 * range. If this instance encodes a date outside of that range, this method will return 5565 * NaN. 5566 * 5567 * @return {number} a number giving the extended unix time, or NaN if the date is outside 5568 * the valid extended unix time range 5569 */ 5570 getTimeExtended: function() { 5571 var jd = this.getJulianDay(); 5572 5573 // test if earlier than Jan 1, 1970 - 100 million days 5574 // or later than Jan 1, 1970 + 100 million days 5575 if (jd < -97559412.5 || jd > 102440587.5) { 5576 return NaN; 5577 } 5578 5579 // avoid the rounding errors in the floating point math by only using 5580 // the whole days from the rd, and then calculating the milliseconds directly 5581 return Math.round((jd - 2440587.5) * 86400000); 5582 }, 5583 5584 /** 5585 * Return the Julian Day equivalent to this calendar date as a number. 5586 * This returns the julian day in UTC. 5587 * 5588 * @return {number} the julian date equivalent of this date 5589 */ 5590 getJulianDay: function() { 5591 return this.rd + this.epoch; 5592 }, 5593 5594 /** 5595 * Return the Rata Die (fixed day) number of this RD date. 5596 * 5597 * @return {number} the rd date as a number 5598 */ 5599 getRataDie: function() { 5600 return this.rd; 5601 } 5602 }; 5603 5604 5605 /*< GregRataDie.js */ 5606 /* 5607 * gregratadie.js - Represent the RD date number in the Gregorian calendar 5608 * 5609 * Copyright © 2014-2015, JEDLSoft 5610 * 5611 * Licensed under the Apache License, Version 2.0 (the "License"); 5612 * you may not use this file except in compliance with the License. 5613 * You may obtain a copy of the License at 5614 * 5615 * http://www.apache.org/licenses/LICENSE-2.0 5616 * 5617 * Unless required by applicable law or agreed to in writing, software 5618 * distributed under the License is distributed on an "AS IS" BASIS, 5619 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5620 * 5621 * See the License for the specific language governing permissions and 5622 * limitations under the License. 5623 */ 5624 5625 /* !depends 5626 ilib.js 5627 GregorianCal.js 5628 RataDie.js 5629 MathUtils.js 5630 */ 5631 5632 5633 /** 5634 * @class 5635 * Construct a new Gregorian RD date number object. The constructor parameters can 5636 * contain any of the following properties: 5637 * 5638 * <ul> 5639 * <li><i>unixtime<i> - sets the time of this instance according to the given 5640 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 5641 * 5642 * <li><i>julianday</i> - sets the time of this instance according to the given 5643 * Julian Day instance or the Julian Day given as a float 5644 * 5645 * <li><i>year</i> - any integer, including 0 5646 * 5647 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 5648 * 5649 * <li><i>day</i> - 1 to 31 5650 * 5651 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 5652 * is always done with an unambiguous 24 hour representation 5653 * 5654 * <li><i>minute</i> - 0 to 59 5655 * 5656 * <li><i>second</i> - 0 to 59 5657 * 5658 * <li><i>millisecond</i> - 0 to 999 5659 * 5660 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 5661 * </ul> 5662 * 5663 * If the constructor is called with another Gregorian date instance instead of 5664 * a parameter block, the other instance acts as a parameter block and its 5665 * settings are copied into the current instance.<p> 5666 * 5667 * If the constructor is called with no arguments at all or if none of the 5668 * properties listed above are present, then the RD is calculate based on 5669 * the current date at the time of instantiation. <p> 5670 * 5671 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 5672 * specified in the params, it is assumed that they have the smallest possible 5673 * value in the range for the property (zero or one).<p> 5674 * 5675 * 5676 * @private 5677 * @constructor 5678 * @extends RataDie 5679 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian RD date 5680 */ 5681 var GregRataDie = function(params) { 5682 this.cal = params && params.cal || new GregorianCal(); 5683 /** @type {number|undefined} */ 5684 this.rd = NaN; 5685 RataDie.call(this, params); 5686 }; 5687 5688 GregRataDie.prototype = new RataDie(); 5689 GregRataDie.prototype.parent = RataDie; 5690 GregRataDie.prototype.constructor = GregRataDie; 5691 5692 /** 5693 * the cumulative lengths of each month, for a non-leap year 5694 * @private 5695 * @const 5696 * @type Array.<number> 5697 */ 5698 GregRataDie.cumMonthLengths = [ 5699 0, /* Jan */ 5700 31, /* Feb */ 5701 59, /* Mar */ 5702 90, /* Apr */ 5703 120, /* May */ 5704 151, /* Jun */ 5705 181, /* Jul */ 5706 212, /* Aug */ 5707 243, /* Sep */ 5708 273, /* Oct */ 5709 304, /* Nov */ 5710 334, /* Dec */ 5711 365 5712 ]; 5713 5714 /** 5715 * the cumulative lengths of each month, for a leap year 5716 * @private 5717 * @const 5718 * @type Array.<number> 5719 */ 5720 GregRataDie.cumMonthLengthsLeap = [ 5721 0, /* Jan */ 5722 31, /* Feb */ 5723 60, /* Mar */ 5724 91, /* Apr */ 5725 121, /* May */ 5726 152, /* Jun */ 5727 182, /* Jul */ 5728 213, /* Aug */ 5729 244, /* Sep */ 5730 274, /* Oct */ 5731 305, /* Nov */ 5732 335, /* Dec */ 5733 366 5734 ]; 5735 5736 /** 5737 * Calculate the Rata Die (fixed day) number of the given date. 5738 * 5739 * @private 5740 * @param {Object} date the date components to calculate the RD from 5741 */ 5742 GregRataDie.prototype._setDateComponents = function(date) { 5743 var year = parseInt(date.year, 10) || 0; 5744 var month = parseInt(date.month, 10) || 1; 5745 var day = parseInt(date.day, 10) || 1; 5746 var hour = parseInt(date.hour, 10) || 0; 5747 var minute = parseInt(date.minute, 10) || 0; 5748 var second = parseInt(date.second, 10) || 0; 5749 var millisecond = parseInt(date.millisecond, 10) || 0; 5750 5751 var years = 365 * (year - 1) + 5752 Math.floor((year-1)/4) - 5753 Math.floor((year-1)/100) + 5754 Math.floor((year-1)/400); 5755 5756 var dayInYear = (month > 1 ? GregRataDie.cumMonthLengths[month-1] : 0) + 5757 day + 5758 (GregorianCal.prototype.isLeapYear.call(this.cal, year) && month > 2 ? 1 : 0); 5759 var rdtime = (hour * 3600000 + 5760 minute * 60000 + 5761 second * 1000 + 5762 millisecond) / 5763 86400000; 5764 /* 5765 debug("getRataDie: converting " + JSON.stringify(this)); 5766 debug("getRataDie: year is " + years); 5767 debug("getRataDie: day in year is " + dayInYear); 5768 debug("getRataDie: rdtime is " + rdtime); 5769 debug("getRataDie: rd is " + (years + dayInYear + rdtime)); 5770 */ 5771 5772 /** 5773 * @type {number|undefined} the RD number of this Gregorian date 5774 */ 5775 this.rd = years + dayInYear + rdtime; 5776 }; 5777 5778 /** 5779 * Return the rd number of the particular day of the week on or before the 5780 * given rd. eg. The Sunday on or before the given rd. 5781 * @private 5782 * @param {number} rd the rata die date of the reference date 5783 * @param {number} dayOfWeek the day of the week that is being sought relative 5784 * to the current date 5785 * @return {number} the rd of the day of the week 5786 */ 5787 GregRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 5788 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 5789 }; 5790 5791 5792 /*< TimeZone.js */ 5793 /* 5794 * TimeZone.js - Definition of a time zone class 5795 * 5796 * Copyright © 2012-2015, JEDLSoft 5797 * 5798 * Licensed under the Apache License, Version 2.0 (the "License"); 5799 * you may not use this file except in compliance with the License. 5800 * You may obtain a copy of the License at 5801 * 5802 * http://www.apache.org/licenses/LICENSE-2.0 5803 * 5804 * Unless required by applicable law or agreed to in writing, software 5805 * distributed under the License is distributed on an "AS IS" BASIS, 5806 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5807 * 5808 * See the License for the specific language governing permissions and 5809 * limitations under the License. 5810 */ 5811 5812 /* 5813 !depends 5814 ilib.js 5815 Locale.js 5816 LocaleInfo.js 5817 Utils.js 5818 MathUtils.js 5819 JSUtils.js 5820 GregRataDie.js 5821 IString.js 5822 CalendarFactory.js 5823 */ 5824 5825 // !data localeinfo zoneinfo 5826 5827 5828 5829 5830 /** 5831 * @class 5832 * Create a time zone instance. 5833 * 5834 * This class reports and transforms 5835 * information about particular time zones.<p> 5836 * 5837 * The options parameter may contain any of the following properties: 5838 * 5839 * <ul> 5840 * <li><i>id</i> - The id of the requested time zone such as "Europe/London" or 5841 * "America/Los_Angeles". These are taken from the IANA time zone database. (See 5842 * http://www.iana.org/time-zones for more information.) <p> 5843 * 5844 * There is one special 5845 * time zone that is not taken from the IANA database called simply "local". In 5846 * this case, this class will attempt to discover the current time zone and 5847 * daylight savings time settings by calling standard Javascript classes to 5848 * determine the offsets from UTC. 5849 * 5850 * <li><i>locale</i> - The locale for this time zone. 5851 * 5852 * <li><i>offset</i> - Choose the time zone based on the offset from UTC given in 5853 * number of minutes (negative is west, positive is east). 5854 * 5855 * <li><i>onLoad</i> - a callback function to call when the data is fully 5856 * loaded. When the onLoad option is given, this class will attempt to 5857 * load any missing locale data using the ilib loader callback. 5858 * When the data is loaded, the onLoad function is called with the current 5859 * instance as a parameter. 5860 * 5861 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 5862 * asynchronously. If this option is given as "false", then the "onLoad" 5863 * callback must be given, as the instance returned from this constructor will 5864 * not be usable for a while. 5865 * 5866 * <li><i>loadParams</i> - an object containing parameters to pass to the 5867 * loader callback function when locale data is missing. The parameters are not 5868 * interpretted or modified in any way. They are simply passed along. The object 5869 * may contain any property/value pairs as long as the calling code is in 5870 * agreement with the loader callback function as to what those parameters mean. 5871 * </ul> 5872 * 5873 * There is currently no way in the ECMAscript 5874 * standard to tell which exact time zone is currently in use. Choosing the 5875 * id "locale" or specifying an explicit offset will not give a specific time zone, 5876 * as it is impossible to tell with certainty which zone the offsets 5877 * match.<p> 5878 * 5879 * When the id "local" is given or the offset option is specified, this class will 5880 * have the following behaviours: 5881 * <ul> 5882 * <li>The display name will always be given as the RFC822 style, no matter what 5883 * style is requested 5884 * <li>The id will also be returned as the RFC822 style display name 5885 * <li>When the offset is explicitly given, this class will assume the time zone 5886 * does not support daylight savings time, and the offsets will be calculated 5887 * the same way year round. 5888 * <li>When the offset is explicitly given, the inDaylightSavings() method will 5889 * always return false. 5890 * <li>When the id "local" is given, this class will attempt to determine the 5891 * daylight savings time settings by examining the offset from UTC on Jan 1 5892 * and June 1 of the current year. If they are different, this class assumes 5893 * that the local time zone uses DST. When the offset for a particular date is 5894 * requested, it will use the built-in Javascript support to determine the 5895 * offset for that date. 5896 * </ul> 5897 * 5898 * If a more specific time zone is 5899 * needed with display names and known start/stop times for DST, use the "id" 5900 * property instead to specify the time zone exactly. You can perhaps ask the 5901 * user which time zone they prefer so that your app does not need to guess.<p> 5902 * 5903 * If the id and the offset are both not given, the default time zone for the 5904 * locale is retrieved from 5905 * the locale info. If the locale is not specified, the default locale for the 5906 * library is used.<p> 5907 * 5908 * Because this class was designed for use in web sites, and the vast majority 5909 * of dates and times being formatted are recent date/times, this class is simplified 5910 * by not implementing historical time zones. That is, when governments change the 5911 * time zone rules for a particular zone, only the latest such rule is implemented 5912 * in this class. That means that determining the offset for a date that is prior 5913 * to the last change may give the wrong result. Historical time zone calculations 5914 * may be implemented in a later version of iLib if there is enough demand for it, 5915 * but it would entail a much larger set of time zone data that would have to be 5916 * loaded. 5917 * 5918 * 5919 * @constructor 5920 * @param {Object} options Options guiding the construction of this time zone instance 5921 */ 5922 var TimeZone = function(options) { 5923 this.sync = true; 5924 this.locale = new Locale(); 5925 this.isLocal = false; 5926 5927 if (options) { 5928 if (options.locale) { 5929 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 5930 } 5931 5932 if (options.id) { 5933 var id = options.id.toString(); 5934 if (id === 'local') { 5935 this.isLocal = true; 5936 5937 // use standard Javascript Date to figure out the time zone offsets 5938 var now = new Date(), 5939 jan1 = new Date(now.getFullYear(), 0, 1), // months in std JS Date object are 0-based 5940 jun1 = new Date(now.getFullYear(), 5, 1); 5941 5942 // Javascript's method returns the offset backwards, so we have to 5943 // take the negative to get the correct offset 5944 this.offsetJan1 = -jan1.getTimezoneOffset(); 5945 this.offsetJun1 = -jun1.getTimezoneOffset(); 5946 // the offset of the standard time for the time zone is always the one that is closest 5947 // to negative infinity of the two, no matter whether you are in the northern or southern 5948 // hemisphere, east or west 5949 this.offset = Math.min(this.offsetJan1, this.offsetJun1); 5950 } 5951 this.id = id; 5952 } else if (options.offset) { 5953 this.offset = (typeof(options.offset) === 'string') ? parseInt(options.offset, 10) : options.offset; 5954 this.id = this.getDisplayName(undefined, undefined); 5955 } 5956 5957 if (typeof(options.sync) !== 'undefined') { 5958 this.sync = !!options.sync; 5959 } 5960 5961 this.loadParams = options.loadParams; 5962 this.onLoad = options.onLoad; 5963 } 5964 5965 //console.log("timezone: locale is " + this.locale); 5966 5967 if (!this.id) { 5968 new LocaleInfo(this.locale, { 5969 sync: this.sync, 5970 onLoad: ilib.bind(this, function (li) { 5971 this.id = li.getTimeZone() || "Etc/UTC"; 5972 this._loadtzdata(); 5973 }) 5974 }); 5975 } else { 5976 this._loadtzdata(); 5977 } 5978 5979 //console.log("localeinfo is: " + JSON.stringify(this.locinfo)); 5980 //console.log("id is: " + JSON.stringify(this.id)); 5981 }; 5982 5983 /* 5984 * Explanation of the compressed time zone info properties. 5985 * { 5986 * "o": "8:0", // offset from UTC 5987 * "f": "W{c}T", // standard abbreviation. For time zones that observe DST, the {c} replacement is replaced with the 5988 * // letter in the e.c or s.c properties below 5989 * "e": { // info about the end of DST 5990 * "j": 78322.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 5991 * // "t" properties, but not both sets. 5992 * "m": 3, // month that it ends 5993 * "r": "l0", // rule for the day it ends "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 5994 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 5995 * "t": "2:0", // time of day that the DST turns off, hours:minutes 5996 * "c": "S" // character to replace into the abbreviation for standard time 5997 * }, 5998 * "s": { // info about the start of DST 5999 * "j": 78189.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 6000 * // "t" properties, but not both sets. 6001 * "m": 10, // month that it starts 6002 * "r": "l0", // rule for the day it starts "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 6003 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 6004 * "t": "2:0", // time of day that the DST turns on, hours:minutes 6005 * "v": "1:0", // amount of time saved in hours:minutes 6006 * "c": "D" // character to replace into the abbreviation for daylight time 6007 * }, 6008 * "c": "AU", // ISO code for the country that contains this time zone 6009 * "n": "W. Australia {c} Time" 6010 * // long English name of the zone. The {c} replacement is for the word "Standard" or "Daylight" as appropriate 6011 * } 6012 */ 6013 TimeZone.prototype._loadtzdata = function () { 6014 var zoneName = this.id.replace(/-/g, "m").replace(/\+/g, "p"); 6015 // console.log("id is: " + JSON.stringify(this.id)); 6016 // console.log("zoneinfo is: " + JSON.stringify(ilib.data.zoneinfo[zoneName])); 6017 if (!ilib.data.zoneinfo[zoneName] && typeof(this.offset) === 'undefined') { 6018 Utils.loadData({ 6019 object: TimeZone, 6020 nonlocale: true, // locale independent 6021 name: "zoneinfo/" + this.id + ".json", 6022 sync: this.sync, 6023 loadParams: this.loadParams, 6024 callback: ilib.bind(this, function (tzdata) { 6025 if (tzdata && !JSUtils.isEmpty(tzdata)) { 6026 ilib.data.zoneinfo[zoneName] = tzdata; 6027 } 6028 this._initZone(zoneName); 6029 }) 6030 }); 6031 } else { 6032 this._initZone(zoneName); 6033 } 6034 }; 6035 6036 TimeZone.prototype._initZone = function(zoneName) { 6037 /** 6038 * @private 6039 * @type {{o:string,f:string,e:Object.<{m:number,r:string,t:string,z:string}>,s:Object.<{m:number,r:string,t:string,z:string,v:string,c:string}>,c:string,n:string}} 6040 */ 6041 this.zone = ilib.data.zoneinfo[zoneName]; 6042 if (!this.zone && typeof(this.offset) === 'undefined') { 6043 this.id = "Etc/UTC"; 6044 this.zone = ilib.data.zoneinfo[this.id]; 6045 } 6046 6047 this._calcDSTSavings(); 6048 6049 if (typeof(this.offset) === 'undefined' && this.zone.o) { 6050 var offsetParts = this._offsetStringToObj(this.zone.o); 6051 /** 6052 * @private 6053 * @type {number} raw offset from UTC without DST, in minutes 6054 */ 6055 this.offset = (Math.abs(offsetParts.h || 0) * 60 + (offsetParts.m || 0)) * MathUtils.signum(offsetParts.h || 0); 6056 } 6057 6058 if (this.onLoad && typeof(this.onLoad) === 'function') { 6059 this.onLoad(this); 6060 } 6061 }; 6062 6063 /** @private */ 6064 TimeZone._marshallIds = function (country, sync, callback) { 6065 var tz, ids = []; 6066 6067 if (!country) { 6068 // local is a special zone meaning "the local time zone according to the JS engine we are running upon" 6069 ids.push("local"); 6070 for (tz in ilib.data.timezones) { 6071 if (ilib.data.timezones[tz]) { 6072 ids.push(ilib.data.timezones[tz]); 6073 } 6074 } 6075 if (typeof(callback) === 'function') { 6076 callback(ids); 6077 } 6078 } else { 6079 if (!ilib.data.zoneinfo.zonetab) { 6080 Utils.loadData({ 6081 object: TimeZone, 6082 nonlocale: true, // locale independent 6083 name: "zoneinfo/zonetab.json", 6084 sync: sync, 6085 callback: ilib.bind(this, function (tzdata) { 6086 if (tzdata) { 6087 ilib.data.zoneinfo.zonetab = tzdata; 6088 } 6089 6090 ids = ilib.data.zoneinfo.zonetab[country]; 6091 6092 if (typeof(callback) === 'function') { 6093 callback(ids); 6094 } 6095 }) 6096 }); 6097 } else { 6098 ids = ilib.data.zoneinfo.zonetab[country]; 6099 if (typeof(callback) === 'function') { 6100 callback(ids); 6101 } 6102 } 6103 } 6104 6105 return ids; 6106 }; 6107 6108 /** 6109 * Return an array of available zone ids that the constructor knows about. 6110 * The country parameter is optional. If it is not given, all time zones will 6111 * be returned. If it specifies a country code, then only time zones for that 6112 * country will be returned. 6113 * 6114 * @param {string|undefined} country country code for which time zones are being sought 6115 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 6116 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 6117 * @return {Array.<string>} an array of zone id strings 6118 */ 6119 TimeZone.getAvailableIds = function (country, sync, onLoad) { 6120 var tz, ids = []; 6121 6122 if (typeof(sync) !== 'boolean') { 6123 sync = true; 6124 } 6125 6126 if (ilib.data.timezones.length === 0) { 6127 if (typeof(ilib._load) !== 'undefined' && typeof(ilib._load.listAvailableFiles) === 'function') { 6128 ilib._load.listAvailableFiles(sync, function(hash) { 6129 for (var dir in hash) { 6130 var files = hash[dir]; 6131 if (ilib.isArray(files)) { 6132 files.forEach(function (filename) { 6133 if (filename && filename.match(/^zoneinfo/)) { 6134 ilib.data.timezones.push(filename.replace(/^zoneinfo\//, "").replace(/\.json$/, "")); 6135 } 6136 }); 6137 } 6138 } 6139 ids = TimeZone._marshallIds(country, sync, onLoad); 6140 }); 6141 } else { 6142 for (tz in ilib.data.zoneinfo) { 6143 if (ilib.data.zoneinfo[tz]) { 6144 ilib.data.timezones.push(tz); 6145 } 6146 } 6147 ids = TimeZone._marshallIds(country, sync, onLoad); 6148 } 6149 } else { 6150 ids = TimeZone._marshallIds(country, sync, onLoad); 6151 } 6152 6153 return ids; 6154 }; 6155 6156 /** 6157 * Return the id used to uniquely identify this time zone. 6158 * @return {string} a unique id for this time zone 6159 */ 6160 TimeZone.prototype.getId = function () { 6161 return this.id.toString(); 6162 }; 6163 6164 /** 6165 * Return the abbreviation that is used for the current time zone on the given date. 6166 * The date may be in DST or during standard time, and many zone names have different 6167 * abbreviations depending on whether or not the date is falls within DST.<p> 6168 * 6169 * There are two styles that are supported: 6170 * 6171 * <ol> 6172 * <li>standard - returns the 3 to 5 letter abbreviation of the time zone name such 6173 * as "CET" for "Central European Time" or "PDT" for "Pacific Daylight Time" 6174 * <li>rfc822 - returns an RFC 822 style time zone specifier, which specifies more 6175 * explicitly what the offset is from UTC 6176 * <li>long - returns the long name of the zone in English 6177 * </ol> 6178 * 6179 * @param {IDate=} date a date to determine if it is in daylight time or standard time 6180 * @param {string=} style one of "standard" or "rfc822". Default if not specified is "standard" 6181 * @return {string} the name of the time zone, abbreviated according to the style 6182 */ 6183 TimeZone.prototype.getDisplayName = function (date, style) { 6184 style = (this.isLocal || typeof(this.zone) === 'undefined') ? "rfc822" : (style || "standard"); 6185 switch (style) { 6186 default: 6187 case 'standard': 6188 if (this.zone.f && this.zone.f !== "zzz") { 6189 if (this.zone.f.indexOf("{c}") !== -1) { 6190 var letter = ""; 6191 letter = this.inDaylightTime(date) ? this.zone.s && this.zone.s.c : this.zone.e && this.zone.e.c; 6192 var temp = new IString(this.zone.f); 6193 return temp.format({c: letter || ""}); 6194 } 6195 return this.zone.f; 6196 } 6197 var temp = "GMT" + this.zone.o; 6198 if (this.inDaylightTime(date)) { 6199 temp += "+" + this.zone.s.v; 6200 } 6201 return temp; 6202 break; 6203 case 'rfc822': 6204 var offset = this.getOffset(date), // includes the DST if applicable 6205 ret = "UTC", 6206 hour = offset.h || 0, 6207 minute = offset.m || 0; 6208 6209 if (hour !== 0) { 6210 ret += (hour > 0) ? "+" : "-"; 6211 if (Math.abs(hour) < 10) { 6212 ret += "0"; 6213 } 6214 ret += (hour < 0) ? -hour : hour; 6215 if (minute < 10) { 6216 ret += "0"; 6217 } 6218 ret += minute; 6219 } 6220 return ret; 6221 case 'long': 6222 if (this.zone.n) { 6223 if (this.zone.n.indexOf("{c}") !== -1) { 6224 var str = this.inDaylightTime(date) ? "Daylight" : "Standard"; 6225 var temp = new IString(this.zone.n); 6226 return temp.format({c: str || ""}); 6227 } 6228 return this.zone.n; 6229 } 6230 var temp = "GMT" + this.zone.o; 6231 if (this.inDaylightTime(date)) { 6232 temp += "+" + this.zone.s.v; 6233 } 6234 return temp; 6235 break; 6236 } 6237 }; 6238 6239 /** 6240 * Convert the offset string to an object with an h, m, and possibly s property 6241 * to indicate the hours, minutes, and seconds. 6242 * 6243 * @private 6244 * @param {string} str the offset string to convert to an object 6245 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset for the zone at 6246 * the given date/time, in hours, minutes, and seconds 6247 */ 6248 TimeZone.prototype._offsetStringToObj = function (str) { 6249 var offsetParts = (typeof(str) === 'string') ? str.split(":") : [], 6250 ret = {h:0}, 6251 temp; 6252 6253 if (offsetParts.length > 0) { 6254 ret.h = parseInt(offsetParts[0], 10); 6255 if (offsetParts.length > 1) { 6256 temp = parseInt(offsetParts[1], 10); 6257 if (temp) { 6258 ret.m = temp; 6259 } 6260 if (offsetParts.length > 2) { 6261 temp = parseInt(offsetParts[2], 10); 6262 if (temp) { 6263 ret.s = temp; 6264 } 6265 } 6266 } 6267 } 6268 6269 return ret; 6270 }; 6271 6272 /** 6273 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 6274 * time is in effect at the given date/time, this method will return the offset value 6275 * adjusted by the amount of daylight saving. 6276 * @param {IDate=} date the date for which the offset is needed 6277 * @return {Object.<{h:number,m:number}>} an object giving the offset for the zone at 6278 * the given date/time, in hours, minutes, and seconds 6279 */ 6280 TimeZone.prototype.getOffset = function (date) { 6281 if (!date) { 6282 return this.getRawOffset(); 6283 } 6284 var offset = this.getOffsetMillis(date)/60000; 6285 6286 var hours = MathUtils.down(offset/60), 6287 minutes = Math.abs(offset) - Math.abs(hours)*60; 6288 6289 var ret = { 6290 h: hours 6291 }; 6292 if (minutes != 0) { 6293 ret.m = minutes; 6294 } 6295 return ret; 6296 }; 6297 6298 /** 6299 * Returns the offset of this time zone from UTC at the given date/time expressed in 6300 * milliseconds. If daylight saving 6301 * time is in effect at the given date/time, this method will return the offset value 6302 * adjusted by the amount of daylight saving. Negative numbers indicate offsets west 6303 * of UTC and conversely, positive numbers indicate offset east of UTC. 6304 * 6305 * @param {IDate=} date the date for which the offset is needed, or null for the 6306 * present date 6307 * @return {number} the number of milliseconds of offset from UTC that the given date is 6308 */ 6309 TimeZone.prototype.getOffsetMillis = function (date) { 6310 var ret; 6311 6312 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 6313 // well if we are in the overlap time at the end of DST 6314 if (this.isLocal && typeof(date.dst) === 'undefined') { 6315 var d = (!date) ? new Date() : new Date(date.getTimeExtended()); 6316 return -d.getTimezoneOffset() * 60000; 6317 } 6318 6319 ret = this.offset; 6320 6321 if (date && this.inDaylightTime(date)) { 6322 ret += this.dstSavings; 6323 } 6324 6325 return ret * 60000; 6326 }; 6327 6328 /** 6329 * Return the offset in milliseconds when the date has an RD number in wall 6330 * time rather than in UTC time. 6331 * @protected 6332 * @param date the date to check in wall time 6333 * @returns {number} the number of milliseconds of offset from UTC that the given date is 6334 */ 6335 TimeZone.prototype._getOffsetMillisWallTime = function (date) { 6336 var ret; 6337 6338 ret = this.offset; 6339 6340 if (date && this.inDaylightTime(date, true)) { 6341 ret += this.dstSavings; 6342 } 6343 6344 return ret * 60000; 6345 }; 6346 6347 /** 6348 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 6349 * time is in effect at the given date/time, this method will return the offset value 6350 * adjusted by the amount of daylight saving. 6351 * @param {IDate=} date the date for which the offset is needed 6352 * @return {string} the offset for the zone at the given date/time as a string in the 6353 * format "h:m:s" 6354 */ 6355 TimeZone.prototype.getOffsetStr = function (date) { 6356 var offset = this.getOffset(date), 6357 ret; 6358 6359 ret = offset.h; 6360 if (typeof(offset.m) !== 'undefined') { 6361 ret += ":" + offset.m; 6362 if (typeof(offset.s) !== 'undefined') { 6363 ret += ":" + offset.s; 6364 } 6365 } else { 6366 ret += ":0"; 6367 } 6368 6369 return ret; 6370 }; 6371 6372 /** 6373 * Gets the offset from UTC for this time zone. 6374 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset from 6375 * UTC for this time zone, in hours, minutes, and seconds 6376 */ 6377 TimeZone.prototype.getRawOffset = function () { 6378 var hours = MathUtils.down(this.offset/60), 6379 minutes = Math.abs(this.offset) - Math.abs(hours)*60; 6380 6381 var ret = { 6382 h: hours 6383 }; 6384 if (minutes != 0) { 6385 ret.m = minutes; 6386 } 6387 return ret; 6388 }; 6389 6390 /** 6391 * Gets the offset from UTC for this time zone expressed in milliseconds. Negative numbers 6392 * indicate zones west of UTC, and positive numbers indicate zones east of UTC. 6393 * 6394 * @return {number} an number giving the offset from 6395 * UTC for this time zone in milliseconds 6396 */ 6397 TimeZone.prototype.getRawOffsetMillis = function () { 6398 return this.offset * 60000; 6399 }; 6400 6401 /** 6402 * Gets the offset from UTC for this time zone without DST savings. 6403 * @return {string} the offset from UTC for this time zone, in the format "h:m:s" 6404 */ 6405 TimeZone.prototype.getRawOffsetStr = function () { 6406 var off = this.getRawOffset(); 6407 return off.h + ":" + (off.m || "0"); 6408 }; 6409 6410 /** 6411 * Return the amount of time in hours:minutes that the clock is advanced during 6412 * daylight savings time. 6413 * @return {Object.<{h:number,m:number,s:number}>} the amount of time that the 6414 * clock advances for DST in hours, minutes, and seconds 6415 */ 6416 TimeZone.prototype.getDSTSavings = function () { 6417 if (this.isLocal) { 6418 // take the absolute because the difference in the offsets may be positive or 6419 // negative, depending on the hemisphere 6420 var savings = Math.abs(this.offsetJan1 - this.offsetJun1); 6421 var hours = MathUtils.down(savings/60), 6422 minutes = savings - hours*60; 6423 return { 6424 h: hours, 6425 m: minutes 6426 }; 6427 } else if (this.zone && this.zone.s) { 6428 return this._offsetStringToObj(this.zone.s.v); // this.zone.start.savings 6429 } 6430 return {h:0}; 6431 }; 6432 6433 /** 6434 * Return the amount of time in hours:minutes that the clock is advanced during 6435 * daylight savings time. 6436 * @return {string} the amount of time that the clock advances for DST in the 6437 * format "h:m:s" 6438 */ 6439 TimeZone.prototype.getDSTSavingsStr = function () { 6440 if (this.isLocal) { 6441 var savings = this.getDSTSavings(); 6442 return savings.h + ":" + savings.m; 6443 } else if (typeof(this.offset) !== 'undefined' && this.zone && this.zone.s) { 6444 return this.zone.s.v; // this.zone.start.savings 6445 } 6446 return "0:0"; 6447 }; 6448 6449 /** 6450 * return the rd of the start of DST transition for the given year 6451 * @protected 6452 * @param {Object} rule set of rules 6453 * @param {number} year year to check 6454 * @return {number} the rd of the start of DST for the year 6455 */ 6456 TimeZone.prototype._calcRuleStart = function (rule, year) { 6457 var type = "=", 6458 weekday = 0, 6459 day, 6460 refDay, 6461 cal, 6462 hour = 0, 6463 minute = 0, 6464 second = 0, 6465 time, 6466 i; 6467 6468 if (typeof(rule.j) !== 'undefined') { 6469 refDay = new GregRataDie({ 6470 julianday: rule.j 6471 }); 6472 } else { 6473 if (rule.r.charAt(0) == 'l' || rule.r.charAt(0) == 'f') { 6474 cal = CalendarFactory({type: "gregorian"}); 6475 type = rule.r.charAt(0); 6476 weekday = parseInt(rule.r.substring(1), 10); 6477 day = (type === 'l') ? cal.getMonLength(rule.m, year) : 1; 6478 //console.log("_calcRuleStart: Calculating the " + 6479 // (rule.r.charAt(0) == 'f' ? "first " : "last ") + weekday + 6480 // " of month " + rule.m); 6481 } else { 6482 i = rule.r.indexOf('<'); 6483 if (i == -1) { 6484 i = rule.r.indexOf('>'); 6485 } 6486 6487 if (i != -1) { 6488 type = rule.r.charAt(i); 6489 weekday = parseInt(rule.r.substring(0, i), 10); 6490 day = parseInt(rule.r.substring(i+1), 10); 6491 //console.log("_calcRuleStart: Calculating the " + weekday + 6492 // type + day + " of month " + rule.m); 6493 } else { 6494 day = parseInt(rule.r, 10); 6495 //console.log("_calcRuleStart: Calculating the " + day + " of month " + rule.m); 6496 } 6497 } 6498 6499 if (rule.t) { 6500 time = rule.t.split(":"); 6501 hour = parseInt(time[0], 10); 6502 if (time.length > 1) { 6503 minute = parseInt(time[1], 10); 6504 if (time.length > 2) { 6505 second = parseInt(time[2], 10); 6506 } 6507 } 6508 } 6509 //console.log("calculating rd of " + year + "/" + rule.m + "/" + day); 6510 refDay = new GregRataDie({ 6511 year: year, 6512 month: rule.m, 6513 day: day, 6514 hour: hour, 6515 minute: minute, 6516 second: second 6517 }); 6518 } 6519 //console.log("refDay is " + JSON.stringify(refDay)); 6520 var d = refDay.getRataDie(); 6521 6522 switch (type) { 6523 case 'l': 6524 case '<': 6525 //console.log("returning " + refDay.onOrBefore(rd, weekday)); 6526 d = refDay.onOrBefore(weekday); 6527 break; 6528 case 'f': 6529 case '>': 6530 //console.log("returning " + refDay.onOrAfterRd(rd, weekday)); 6531 d = refDay.onOrAfter(weekday); 6532 break; 6533 } 6534 return d; 6535 }; 6536 6537 /** 6538 * @private 6539 */ 6540 TimeZone.prototype._calcDSTSavings = function () { 6541 var saveParts = this.getDSTSavings(); 6542 6543 /** 6544 * @private 6545 * @type {number} savings in minutes when DST is in effect 6546 */ 6547 this.dstSavings = (Math.abs(saveParts.h || 0) * 60 + (saveParts.m || 0)) * MathUtils.signum(saveParts.h || 0); 6548 }; 6549 6550 /** 6551 * @private 6552 */ 6553 TimeZone.prototype._getDSTStartRule = function (year) { 6554 // TODO: update this when historic/future zones are supported 6555 return this.zone.s; 6556 }; 6557 6558 /** 6559 * @private 6560 */ 6561 TimeZone.prototype._getDSTEndRule = function (year) { 6562 // TODO: update this when historic/future zones are supported 6563 return this.zone.e; 6564 }; 6565 6566 /** 6567 * Returns whether or not the given date is in daylight saving time for the current 6568 * zone. Note that daylight savings time is observed for the summer. Because 6569 * the seasons are reversed, daylight savings time in the southern hemisphere usually 6570 * runs from the end of the year through New Years into the first few months of the 6571 * next year. This method will correctly calculate the start and end of DST for any 6572 * location. 6573 * 6574 * @param {IDate=} date a date for which the info about daylight time is being sought, 6575 * or undefined to tell whether we are currently in daylight savings time 6576 * @param {boolean=} wallTime if true, then the given date is in wall time. If false or 6577 * undefined, it is in the usual UTC time. 6578 * @return {boolean} true if the given date is in DST for the current zone, and false 6579 * otherwise. 6580 */ 6581 TimeZone.prototype.inDaylightTime = function (date, wallTime) { 6582 var rd, startRd, endRd, year; 6583 6584 if (this.isLocal) { 6585 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 6586 // well if we are in the overlap time at the end of DST, so we have to work around that 6587 // problem by adding in the savings ourselves 6588 var offset = 0; 6589 if (typeof(date.dst) !== 'undefined' && !date.dst) { 6590 offset = this.dstSavings * 60000; 6591 } 6592 6593 var d = new Date(date ? date.getTimeExtended() + offset: undefined); 6594 // the DST offset is always the one that is closest to positive infinity, no matter 6595 // if you are in the northern or southern hemisphere, east or west 6596 var dst = Math.max(this.offsetJan1, this.offsetJun1); 6597 return (-d.getTimezoneOffset() === dst); 6598 } 6599 6600 if (!date || !date.cal || date.cal.type !== "gregorian") { 6601 // convert to Gregorian so that we can tell if it is in DST or not 6602 var time = date && typeof(date.getTimeExtended) === 'function' ? date.getTimeExtended() : undefined; 6603 rd = new GregRataDie({unixtime: time}).getRataDie(); 6604 year = new Date(time).getUTCFullYear(); 6605 } else { 6606 rd = date.rd.getRataDie(); 6607 year = date.year; 6608 } 6609 // rd should be a Gregorian RD number now, in UTC 6610 6611 // if we aren't using daylight time in this zone for the given year, then we are 6612 // not in daylight time 6613 if (!this.useDaylightTime(year)) { 6614 return false; 6615 } 6616 6617 // these calculate the start/end in local wall time 6618 var startrule = this._getDSTStartRule(year); 6619 var endrule = this._getDSTEndRule(year); 6620 startRd = this._calcRuleStart(startrule, year); 6621 endRd = this._calcRuleStart(endrule, year); 6622 6623 if (wallTime) { 6624 // rd is in wall time, so we have to make sure to skip the missing time 6625 // at the start of DST when standard time ends and daylight time begins 6626 startRd += this.dstSavings/1440; 6627 } else { 6628 // rd is in UTC, so we have to convert the start/end to UTC time so 6629 // that they can be compared directly to the UTC rd number of the date 6630 6631 // when DST starts, time is standard time already, so we only have 6632 // to subtract the offset to get to UTC and not worry about the DST savings 6633 startRd -= this.offset/1440; 6634 6635 // when DST ends, time is in daylight time already, so we have to 6636 // subtract the DST savings to get back to standard time, then the 6637 // offset to get to UTC 6638 endRd -= (this.offset + this.dstSavings)/1440; 6639 } 6640 6641 // In the northern hemisphere, the start comes first some time in spring (Feb-Apr), 6642 // then the end some time in the fall (Sept-Nov). In the southern 6643 // hemisphere, it is the other way around because the seasons are reversed. Standard 6644 // time is still in the winter, but the winter months are May-Aug, and daylight 6645 // savings time usually starts Aug-Oct of one year and runs through Mar-May of the 6646 // next year. 6647 if (rd < endRd && endRd - rd <= this.dstSavings/1440 && typeof(date.dst) === 'boolean') { 6648 // take care of the magic overlap time at the end of DST 6649 return date.dst; 6650 } 6651 if (startRd < endRd) { 6652 // northern hemisphere 6653 return (rd >= startRd && rd < endRd) ? true : false; 6654 } 6655 // southern hemisphere 6656 return (rd >= startRd || rd < endRd) ? true : false; 6657 }; 6658 6659 /** 6660 * Returns true if this time zone switches to daylight savings time at some point 6661 * in the year, and false otherwise. 6662 * @param {number} year Whether or not the time zone uses daylight time in the given year. If 6663 * this parameter is not given, the current year is assumed. 6664 * @return {boolean} true if the time zone uses daylight savings time 6665 */ 6666 TimeZone.prototype.useDaylightTime = function (year) { 6667 6668 // this zone uses daylight savings time iff there is a rule defining when to start 6669 // and when to stop the DST 6670 return (this.isLocal && this.offsetJan1 !== this.offsetJun1) || 6671 (typeof(this.zone) !== 'undefined' && 6672 typeof(this.zone.s) !== 'undefined' && 6673 typeof(this.zone.e) !== 'undefined'); 6674 }; 6675 6676 /** 6677 * Returns the ISO 3166 code of the country for which this time zone is defined. 6678 * @return {string} the ISO 3166 code of the country for this zone 6679 */ 6680 TimeZone.prototype.getCountry = function () { 6681 return this.zone.c; 6682 }; 6683 6684 6685 6686 /*< SearchUtils.js */ 6687 /* 6688 * SearchUtils.js - Misc search utility routines 6689 * 6690 * Copyright © 2013-2015, JEDLSoft 6691 * 6692 * Licensed under the Apache License, Version 2.0 (the "License"); 6693 * you may not use this file except in compliance with the License. 6694 * You may obtain a copy of the License at 6695 * 6696 * http://www.apache.org/licenses/LICENSE-2.0 6697 * 6698 * Unless required by applicable law or agreed to in writing, software 6699 * distributed under the License is distributed on an "AS IS" BASIS, 6700 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6701 * 6702 * See the License for the specific language governing permissions and 6703 * limitations under the License. 6704 */ 6705 6706 var SearchUtils = {}; 6707 6708 /** 6709 * Binary search a sorted array for a particular target value. 6710 * If the exact value is not found, it returns the index of the smallest 6711 * entry that is greater than the given target value.<p> 6712 * 6713 * The comparator 6714 * parameter is a function that knows how to compare elements of the 6715 * array and the target. The function should return a value greater than 0 6716 * if the array element is greater than the target, a value less than 0 if 6717 * the array element is less than the target, and 0 if the array element 6718 * and the target are equivalent.<p> 6719 * 6720 * If the comparator function is not specified, this function assumes 6721 * the array and the target are numeric values and should be compared 6722 * as such.<p> 6723 * 6724 * 6725 * @static 6726 * @param {*} target element being sought 6727 * @param {Array} arr the array being searched 6728 * @param {?function(*,*)=} comparator a comparator that is appropriate for comparing two entries 6729 * in the array 6730 * @return the index of the array into which the value would fit if 6731 * inserted, or -1 if given array is not an array or the target is not 6732 * a number 6733 */ 6734 SearchUtils.bsearch = function(target, arr, comparator) { 6735 if (typeof(arr) === 'undefined' || !arr || typeof(target) === 'undefined') { 6736 return -1; 6737 } 6738 6739 var high = arr.length - 1, 6740 low = 0, 6741 mid = 0, 6742 value, 6743 cmp = comparator || SearchUtils.bsearch.numbers; 6744 6745 while (low <= high) { 6746 mid = Math.floor((high+low)/2); 6747 value = cmp(arr[mid], target); 6748 if (value > 0) { 6749 high = mid - 1; 6750 } else if (value < 0) { 6751 low = mid + 1; 6752 } else { 6753 return mid; 6754 } 6755 } 6756 6757 return low; 6758 }; 6759 6760 /** 6761 * Returns whether or not the given element is greater than, less than, 6762 * or equal to the given target.<p> 6763 * 6764 * @private 6765 * @static 6766 * @param {number} element the element being tested 6767 * @param {number} target the target being sought 6768 */ 6769 SearchUtils.bsearch.numbers = function(element, target) { 6770 return element - target; 6771 }; 6772 6773 /** 6774 * Do a bisection search of a function for a particular target value.<p> 6775 * 6776 * The function to search is a function that takes a numeric parameter, 6777 * does calculations, and returns gives a numeric result. The 6778 * function should should be smooth and not have any discontinuities 6779 * between the low and high values of the parameter. 6780 * 6781 * 6782 * @static 6783 * @param {number} target value being sought 6784 * @param {number} low the lower bounds to start searching 6785 * @param {number} high the upper bounds to start searching 6786 * @param {number} precision minimum precision to support. Use 0 if you want to use the default. 6787 * @param {?function(number)=} func function to search 6788 * @return an approximation of the input value to the function that gives the desired 6789 * target output value, correct to within the error range of Javascript floating point 6790 * arithmetic, or NaN if there was some error 6791 */ 6792 SearchUtils.bisectionSearch = function(target, low, high, precision, func) { 6793 if (typeof(target) !== 'number' || 6794 typeof(low) !== 'number' || 6795 typeof(high) !== 'number' || 6796 typeof(func) !== 'function') { 6797 return NaN; 6798 } 6799 6800 var mid = 0, 6801 value, 6802 pre = precision > 0 ? precision : 1e-13; 6803 6804 do { 6805 mid = (high+low)/2; 6806 value = func(mid); 6807 if (value > target) { 6808 high = mid; 6809 } else if (value < target) { 6810 low = mid; 6811 } 6812 } while (high - low > pre); 6813 6814 return mid; 6815 }; 6816 6817 6818 6819 /*< GregorianDate.js */ 6820 /* 6821 * GregorianDate.js - Represent a date in the Gregorian calendar 6822 * 6823 * Copyright © 2012-2015, JEDLSoft 6824 * 6825 * Licensed under the Apache License, Version 2.0 (the "License"); 6826 * you may not use this file except in compliance with the License. 6827 * You may obtain a copy of the License at 6828 * 6829 * http://www.apache.org/licenses/LICENSE-2.0 6830 * 6831 * Unless required by applicable law or agreed to in writing, software 6832 * distributed under the License is distributed on an "AS IS" BASIS, 6833 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6834 * 6835 * See the License for the specific language governing permissions and 6836 * limitations under the License. 6837 */ 6838 6839 /* !depends 6840 ilib.js 6841 IDate.js 6842 GregorianCal.js 6843 SearchUtils.js 6844 MathUtils.js 6845 Locale.js 6846 LocaleInfo.js 6847 JulianDay.js 6848 GregRataDie.js 6849 TimeZone.js 6850 */ 6851 6852 6853 6854 6855 /** 6856 * @class 6857 * Construct a new Gregorian date object. The constructor parameters can 6858 * contain any of the following properties: 6859 * 6860 * <ul> 6861 * <li><i>unixtime<i> - sets the time of this instance according to the given 6862 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 6863 * 6864 * <li><i>julianday</i> - sets the time of this instance according to the given 6865 * Julian Day instance or the Julian Day given as a float 6866 * 6867 * <li><i>year</i> - any integer, including 0 6868 * 6869 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 6870 * 6871 * <li><i>day</i> - 1 to 31 6872 * 6873 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 6874 * is always done with an unambiguous 24 hour representation 6875 * 6876 * <li><i>minute</i> - 0 to 59 6877 * 6878 * <li><i>second</i> - 0 to 59 6879 * 6880 * <li><i>millisecond</i> - 0 to 999 6881 * 6882 * <li><i>dst</i> - boolean used to specify whether the given time components are 6883 * intended to be in daylight time or not. This is only used in the overlap 6884 * time when transitioning from DST to standard time, and the time components are 6885 * ambiguous. Otherwise at all other times of the year, this flag is ignored. 6886 * If you specify the date using unix time (UTC) or a julian day, then the time is 6887 * already unambiguous and this flag does not need to be specified. 6888 * <p> 6889 * For example, in the US, the transition out of daylight savings time 6890 * in 2014 happens at Nov 2, 2014 2:00am Daylight Time, when the time falls 6891 * back to Nov 2, 2014 1:00am Standard Time. If you give a date/time components as 6892 * "Nov 2, 2014 1:30am", then there are two 1:30am times in that day, and you would 6893 * have to give the standard flag to indicate which of those two you mean. 6894 * (dst=true means daylight time, dst=false means standard time). 6895 * 6896 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 6897 * of this gregorian date. The date/time is kept in the local time. The time zone 6898 * is used later if this date is formatted according to a different time zone and 6899 * the difference has to be calculated, or when the date format has a time zone 6900 * component in it. 6901 * 6902 * <li><i>locale</i> - locale for this gregorian date. If the time zone is not 6903 * given, it can be inferred from this locale. For locales that span multiple 6904 * time zones, the one with the largest population is chosen as the one that 6905 * represents the locale. 6906 * 6907 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 6908 * </ul> 6909 * 6910 * If the constructor is called with another Gregorian date instance instead of 6911 * a parameter block, the other instance acts as a parameter block and its 6912 * settings are copied into the current instance.<p> 6913 * 6914 * If the constructor is called with no arguments at all or if none of the 6915 * properties listed above 6916 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 6917 * components are 6918 * filled in with the current date at the time of instantiation. Note that if 6919 * you do not give the time zone when defaulting to the current time and the 6920 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 6921 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 6922 * Mean Time").<p> 6923 * 6924 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 6925 * specified in the params, it is assumed that they have the smallest possible 6926 * value in the range for the property (zero or one).<p> 6927 * 6928 * 6929 * @constructor 6930 * @extends IDate 6931 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian date 6932 */ 6933 var GregorianDate = function(params) { 6934 this.cal = new GregorianCal(); 6935 this.timezone = "local"; 6936 6937 if (params) { 6938 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 6939 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 6940 return; 6941 } 6942 if (params.locale) { 6943 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 6944 var li = new LocaleInfo(this.locale); 6945 this.timezone = li.getTimeZone(); 6946 } 6947 if (params.timezone) { 6948 this.timezone = params.timezone.toString(); 6949 } 6950 6951 if (params.year || params.month || params.day || params.hour || 6952 params.minute || params.second || params.millisecond ) { 6953 this.year = parseInt(params.year, 10) || 0; 6954 this.month = parseInt(params.month, 10) || 1; 6955 this.day = parseInt(params.day, 10) || 1; 6956 this.hour = parseInt(params.hour, 10) || 0; 6957 this.minute = parseInt(params.minute, 10) || 0; 6958 this.second = parseInt(params.second, 10) || 0; 6959 this.millisecond = parseInt(params.millisecond, 10) || 0; 6960 if (typeof(params.dst) === 'boolean') { 6961 this.dst = params.dst; 6962 } 6963 this.rd = this.newRd(params); 6964 6965 // add the time zone offset to the rd to convert to UTC 6966 this.offset = 0; 6967 if (this.timezone === "local" && typeof(params.dst) === 'undefined') { 6968 // if dst is defined, the intrinsic Date object has no way of specifying which version of a time you mean 6969 // in the overlap time at the end of DST. Do you mean the daylight 1:30am or the standard 1:30am? In this 6970 // case, use the ilib calculations below, which can distinguish between the two properly 6971 var d = new Date(this.year, this.month-1, this.day, this.hour, this.minute, this.second, this.millisecond); 6972 this.offset = -d.getTimezoneOffset() / 1440; 6973 } else { 6974 if (!this.tz) { 6975 this.tz = new TimeZone({id: this.timezone}); 6976 } 6977 // getOffsetMillis requires that this.year, this.rd, and this.dst 6978 // are set in order to figure out which time zone rules apply and 6979 // what the offset is at that point in the year 6980 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 6981 } 6982 if (this.offset !== 0) { 6983 this.rd = this.newRd({ 6984 rd: this.rd.getRataDie() - this.offset 6985 }); 6986 } 6987 } 6988 } 6989 6990 if (!this.rd) { 6991 this.rd = this.newRd(params); 6992 this._calcDateComponents(); 6993 } 6994 }; 6995 6996 GregorianDate.prototype = new IDate({noinstance: true}); 6997 GregorianDate.prototype.parent = IDate; 6998 GregorianDate.prototype.constructor = GregorianDate; 6999 7000 /** 7001 * Return a new RD for this date type using the given params. 7002 * @private 7003 * @param {Object=} params the parameters used to create this rata die instance 7004 * @returns {RataDie} the new RD instance for the given params 7005 */ 7006 GregorianDate.prototype.newRd = function (params) { 7007 return new GregRataDie(params); 7008 }; 7009 7010 /** 7011 * Calculates the Gregorian year for a given rd number. 7012 * @private 7013 * @static 7014 */ 7015 GregorianDate._calcYear = function(rd) { 7016 var days400, 7017 days100, 7018 days4, 7019 years400, 7020 years100, 7021 years4, 7022 years1, 7023 year; 7024 7025 years400 = Math.floor((rd - 1) / 146097); 7026 days400 = MathUtils.mod((rd - 1), 146097); 7027 years100 = Math.floor(days400 / 36524); 7028 days100 = MathUtils.mod(days400, 36524); 7029 years4 = Math.floor(days100 / 1461); 7030 days4 = MathUtils.mod(days100, 1461); 7031 years1 = Math.floor(days4 / 365); 7032 7033 year = 400 * years400 + 100 * years100 + 4 * years4 + years1; 7034 if (years100 !== 4 && years1 !== 4) { 7035 year++; 7036 } 7037 return year; 7038 }; 7039 7040 /** 7041 * @private 7042 */ 7043 GregorianDate.prototype._calcYear = function(rd) { 7044 return GregorianDate._calcYear(rd); 7045 }; 7046 7047 /** 7048 * Calculate the date components for the current time zone 7049 * @private 7050 */ 7051 GregorianDate.prototype._calcDateComponents = function () { 7052 if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { 7053 // console.log("using js Date to calculate offset"); 7054 // use the intrinsic JS Date object to do the tz conversion for us, which 7055 // guarantees that it follows the system tz database settings 7056 var d = new Date(this.rd.getTimeExtended()); 7057 7058 /** 7059 * Year in the Gregorian calendar. 7060 * @type number 7061 */ 7062 this.year = d.getFullYear(); 7063 7064 /** 7065 * The month number, ranging from 1 (January) to 12 (December). 7066 * @type number 7067 */ 7068 this.month = d.getMonth()+1; 7069 7070 /** 7071 * The day of the month. This ranges from 1 to 31. 7072 * @type number 7073 */ 7074 this.day = d.getDate(); 7075 7076 /** 7077 * The hour of the day. This can be a number from 0 to 23, as times are 7078 * stored unambiguously in the 24-hour clock. 7079 * @type number 7080 */ 7081 this.hour = d.getHours(); 7082 7083 /** 7084 * The minute of the hours. Ranges from 0 to 59. 7085 * @type number 7086 */ 7087 this.minute = d.getMinutes(); 7088 7089 /** 7090 * The second of the minute. Ranges from 0 to 59. 7091 * @type number 7092 */ 7093 this.second = d.getSeconds(); 7094 7095 /** 7096 * The millisecond of the second. Ranges from 0 to 999. 7097 * @type number 7098 */ 7099 this.millisecond = d.getMilliseconds(); 7100 7101 this.offset = -d.getTimezoneOffset() / 1440; 7102 } else { 7103 // console.log("using ilib to calculate offset. tz is " + this.timezone); 7104 // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 7105 if (typeof(this.offset) === "undefined") { 7106 // console.log("calculating offset"); 7107 this.year = this._calcYear(this.rd.getRataDie()); 7108 7109 // now offset the RD by the time zone, then recalculate in case we were 7110 // near the year boundary 7111 if (!this.tz) { 7112 this.tz = new TimeZone({id: this.timezone}); 7113 } 7114 this.offset = this.tz.getOffsetMillis(this) / 86400000; 7115 // } else { 7116 // console.log("offset is already defined somehow. type is " + typeof(this.offset)); 7117 // console.trace("Stack is this one"); 7118 } 7119 // console.log("offset is " + this.offset); 7120 var rd = this.rd.getRataDie(); 7121 if (this.offset !== 0) { 7122 rd += this.offset; 7123 } 7124 this.year = this._calcYear(rd); 7125 7126 var yearStartRd = this.newRd({ 7127 year: this.year, 7128 month: 1, 7129 day: 1, 7130 cal: this.cal 7131 }); 7132 7133 // remainder is days into the year 7134 var remainder = rd - yearStartRd.getRataDie() + 1; 7135 7136 var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? 7137 GregRataDie.cumMonthLengthsLeap : 7138 GregRataDie.cumMonthLengths; 7139 7140 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 7141 remainder = remainder - cumulative[this.month-1]; 7142 7143 this.day = Math.floor(remainder); 7144 remainder -= this.day; 7145 // now convert to milliseconds for the rest of the calculation 7146 remainder = Math.round(remainder * 86400000); 7147 7148 this.hour = Math.floor(remainder/3600000); 7149 remainder -= this.hour * 3600000; 7150 7151 this.minute = Math.floor(remainder/60000); 7152 remainder -= this.minute * 60000; 7153 7154 this.second = Math.floor(remainder/1000); 7155 remainder -= this.second * 1000; 7156 7157 this.millisecond = Math.floor(remainder); 7158 } 7159 }; 7160 7161 /** 7162 * Return the day of the week of this date. The day of the week is encoded 7163 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 7164 * 7165 * @return {number} the day of the week 7166 */ 7167 GregorianDate.prototype.getDayOfWeek = function() { 7168 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 7169 return MathUtils.mod(rd, 7); 7170 }; 7171 7172 /** 7173 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 7174 * 365, regardless of months or weeks, etc. That is, January 1st is day 1, and 7175 * December 31st is 365 in regular years, or 366 in leap years. 7176 * @return {number} the ordinal day of the year 7177 */ 7178 GregorianDate.prototype.getDayOfYear = function() { 7179 var cumulativeMap = this.cal.isLeapYear(this.year) ? 7180 GregRataDie.cumMonthLengthsLeap : 7181 GregRataDie.cumMonthLengths; 7182 7183 return cumulativeMap[this.month-1] + this.day; 7184 }; 7185 7186 /** 7187 * Return the era for this date as a number. The value for the era for Gregorian 7188 * calendars is -1 for "before the common era" (BCE) and 1 for "the common era" (CE). 7189 * BCE dates are any date before Jan 1, 1 CE. In the proleptic Gregorian calendar, 7190 * there is a year 0, so any years that are negative or zero are BCE. In the Julian 7191 * calendar, there is no year 0. Instead, the calendar goes straight from year -1 to 7192 * 1. 7193 * @return {number} 1 if this date is in the common era, -1 if it is before the 7194 * common era 7195 */ 7196 GregorianDate.prototype.getEra = function() { 7197 return (this.year < 1) ? -1 : 1; 7198 }; 7199 7200 /** 7201 * Return the name of the calendar that governs this date. 7202 * 7203 * @return {string} a string giving the name of the calendar 7204 */ 7205 GregorianDate.prototype.getCalendar = function() { 7206 return "gregorian"; 7207 }; 7208 7209 // register with the factory method 7210 IDate._constructors["gregorian"] = GregorianDate; 7211 7212 7213 /*< DateFactory.js */ 7214 /* 7215 * DateFactory.js - Factory class to create the right subclasses of a date for any 7216 * calendar or locale. 7217 * 7218 * Copyright © 2012-2015, JEDLSoft 7219 * 7220 * Licensed under the Apache License, Version 2.0 (the "License"); 7221 * you may not use this file except in compliance with the License. 7222 * You may obtain a copy of the License at 7223 * 7224 * http://www.apache.org/licenses/LICENSE-2.0 7225 * 7226 * Unless required by applicable law or agreed to in writing, software 7227 * distributed under the License is distributed on an "AS IS" BASIS, 7228 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7229 * 7230 * See the License for the specific language governing permissions and 7231 * limitations under the License. 7232 */ 7233 7234 /* !depends ilib.js Locale.js LocaleInfo.js JulianDay.js JSUtils.js CalendarFactory.js IDate.js GregorianDate.js*/ 7235 7236 7237 7238 // Statically depend on these even though we don't use them 7239 // to guarantee they are loaded into the cache already. 7240 7241 /** 7242 * Factory method to create a new instance of a date subclass.<p> 7243 * 7244 * The options parameter can be an object that contains the following 7245 * properties: 7246 * 7247 * <ul> 7248 * <li><i>type</i> - specify the type/calendar of the date desired. The 7249 * list of valid values changes depending on which calendars are 7250 * defined. When assembling your iliball.js, include those date type 7251 * you wish to use in your program or web page, and they will register 7252 * themselves with this factory method. The "gregorian", 7253 * and "julian" calendars are all included by default, as they are the 7254 * standard calendars for much of the world. If not specified, the type 7255 * of the date returned is the one that is appropriate for the locale. 7256 * This property may also be given as "calendar" instead of "type". 7257 * 7258 * <li><i>onLoad</i> - a callback function to call when the date object is fully 7259 * loaded. When the onLoad option is given, the date factory will attempt to 7260 * load any missing locale data using the ilib loader callback. 7261 * When the constructor is done (even if the data is already preassembled), the 7262 * onLoad function is called with the current instance as a parameter, so this 7263 * callback can be used with preassembled or dynamic loading or a mix of the two. 7264 * 7265 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 7266 * asynchronously. If this option is given as "false", then the "onLoad" 7267 * callback must be given, as the instance returned from this constructor will 7268 * not be usable for a while. 7269 * 7270 * <li><i>loadParams</i> - an object containing parameters to pass to the 7271 * loader callback function when locale data is missing. The parameters are not 7272 * interpretted or modified in any way. They are simply passed along. The object 7273 * may contain any property/value pairs as long as the calling code is in 7274 * agreement with the loader callback function as to what those parameters mean. 7275 * </ul> 7276 * 7277 * The options object is also passed down to the date constructor, and 7278 * thus can contain the the properties as the date object being instantiated. 7279 * See the documentation for {@link GregorianDate}, and other 7280 * subclasses for more details on other parameter that may be passed in.<p> 7281 * 7282 * Please note that if you do not give the type parameter, this factory 7283 * method will create a date object that is appropriate for the calendar 7284 * that is most commonly used in the specified or current ilib locale. 7285 * For example, in Thailand, the most common calendar is the Thai solar 7286 * calendar. If the current locale is "th-TH" (Thai for Thailand) and you 7287 * use this factory method to construct a new date without specifying the 7288 * type, it will automatically give you back an instance of 7289 * {@link ThaiSolarDate}. This is convenient because you do not 7290 * need to know which locales use which types of dates. In fact, you 7291 * should always use this factory method to make new date instances unless 7292 * you know that you specifically need a date in a particular calendar.<p> 7293 * 7294 * Also note that when you pass in the date components such as year, month, 7295 * day, etc., these components should be appropriate for the given date 7296 * being instantiated. That is, in our Thai example in the previous 7297 * paragraph, the year and such should be given as a Thai solar year, not 7298 * the Gregorian year that you get from the Javascript Date class. In 7299 * order to initialize a date instance when you don't know what subclass 7300 * will be instantiated for the locale, use a parameter such as "unixtime" 7301 * or "julianday" which are unambiguous and based on UTC time, instead of 7302 * the year/month/date date components. The date components for that UTC 7303 * time will be calculated and the time zone offset will be automatically 7304 * factored in. 7305 * 7306 * @static 7307 * @param {Object=} options options controlling the construction of this instance, or 7308 * undefined to use the default options 7309 * @return {IDate} an instance of a calendar object of the appropriate type 7310 */ 7311 var DateFactory = function(options) { 7312 var locale, 7313 type, 7314 cons, 7315 sync = true, 7316 obj; 7317 7318 if (options) { 7319 if (options.locale) { 7320 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 7321 } 7322 7323 type = options.type || options.calendar; 7324 7325 if (typeof(options.sync) === 'boolean') { 7326 sync = options.sync; 7327 } 7328 } 7329 7330 if (!locale) { 7331 locale = new Locale(); // default locale 7332 } 7333 7334 if (!type) { 7335 new LocaleInfo(locale, { 7336 sync: sync, 7337 loadParams: options && options.loadParams, 7338 onLoad: ilib.bind(this, function(info) { 7339 type = info.getCalendar(); 7340 7341 obj = DateFactory._init(type, options); 7342 7343 if (options && typeof(options.onLoad) === 'function') { 7344 options.onLoad(obj); 7345 } 7346 }) 7347 }); 7348 } else { 7349 obj = DateFactory._init(type, options); 7350 } 7351 7352 return obj 7353 }; 7354 7355 /** 7356 * Map calendar names to classes to initialize in the dynamic code model. 7357 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 7358 * @private 7359 */ 7360 DateFactory._dynMap = { 7361 "coptic": "Coptic", 7362 "ethiopic": "Ethiopic", 7363 "gregorian": "Gregorian", 7364 "han": "Han", 7365 "hebrew": "Hebrew", 7366 "islamic": "Islamic", 7367 "julian": "Julian", 7368 "persian": "Persian", 7369 "persian-algo": "PersianAlgo", 7370 "thaisolar": "ThaiSolar" 7371 }; 7372 7373 /** 7374 * Dynamically load the code for a calendar and calendar class if necessary. 7375 * @protected 7376 */ 7377 DateFactory._dynLoadDate = function (name) { 7378 if (!IDate._constructors[name]) { 7379 var entry = DateFactory._dynMap[name]; 7380 if (entry) { 7381 IDate._constructors[name] = require("./" + entry + "Date.js"); 7382 } 7383 } 7384 return IDate._constructors[name]; 7385 }; 7386 7387 /** 7388 * @protected 7389 * @static 7390 */ 7391 DateFactory._init = function(type, options) { 7392 var cons; 7393 7394 if (ilib.isDynCode()) { 7395 DateFactory._dynLoadDate(type); 7396 CalendarFactory._dynLoadCalendar(type); 7397 } 7398 7399 cons = IDate._constructors[type]; 7400 7401 // pass the same options through to the constructor so the subclass 7402 // has the ability to do something with if it needs to 7403 return cons && new cons(options); 7404 }; 7405 7406 /** 7407 * Convert JavaScript Date objects and other types into native Dates. This accepts any 7408 * string or number that can be translated by the JavaScript Date class, 7409 * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) 7410 * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object 7411 * containing the normal options to initialize an IDate instance, or null (will 7412 * return null or undefined if input is null or undefined). Normal output is 7413 * a standard native subclass of the IDate object as appropriate for the locale. 7414 * 7415 * @static 7416 * @protected 7417 * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number. 7418 * @param {IString|string=} timezone timezone to use if a new date object is created 7419 * @param {Locale|string=} locale locale to use when constructing an IDate 7420 * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate 7421 */ 7422 DateFactory._dateToIlib = function(inDate, timezone, locale) { 7423 if (typeof(inDate) === 'undefined' || inDate === null) { 7424 return inDate; 7425 } 7426 if (inDate instanceof IDate) { 7427 return inDate; 7428 } 7429 if (typeof(inDate) === 'number') { 7430 return DateFactory({ 7431 unixtime: inDate, 7432 timezone: timezone, 7433 locale: locale 7434 }); 7435 } 7436 if (typeof(inDate) === 'string') { 7437 inDate = new Date(inDate); 7438 } 7439 if (JSUtils.isDate(inDate)) { 7440 return DateFactory({ 7441 unixtime: inDate.getTime(), 7442 timezone: timezone, 7443 locale: locale 7444 }); 7445 } 7446 if (inDate instanceof JulianDay) { 7447 return DateFactory({ 7448 jd: inDate, 7449 timezone: timezone, 7450 locale: locale 7451 }); 7452 } 7453 if (typeof(inDate) === 'object') { 7454 return DateFactory(inDate); 7455 } 7456 return DateFactory({ 7457 unixtime: inDate.getTime(), 7458 timezone: timezone, 7459 locale: locale 7460 }); 7461 }; 7462 7463 7464 /*< ResBundle.js */ 7465 /* 7466 * ResBundle.js - Resource bundle definition 7467 * 7468 * Copyright © 2012-2015, JEDLSoft 7469 * 7470 * Licensed under the Apache License, Version 2.0 (the "License"); 7471 * you may not use this file except in compliance with the License. 7472 * You may obtain a copy of the License at 7473 * 7474 * http://www.apache.org/licenses/LICENSE-2.0 7475 * 7476 * Unless required by applicable law or agreed to in writing, software 7477 * distributed under the License is distributed on an "AS IS" BASIS, 7478 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7479 * 7480 * See the License for the specific language governing permissions and 7481 * limitations under the License. 7482 */ 7483 7484 // !depends ilib.js Locale.js LocaleInfo.js IString.js Utils.js JSUtils.js 7485 7486 // !data pseudomap 7487 7488 7489 7490 7491 /** 7492 * @class 7493 * Create a new resource bundle instance. The resource bundle loads strings 7494 * appropriate for a particular locale and provides them via the getString 7495 * method.<p> 7496 * 7497 * The options object may contain any (or none) of the following properties: 7498 * 7499 * <ul> 7500 * <li><i>locale</i> - The locale of the strings to load. If not specified, the default 7501 * locale is the the default for the web page or app in which the bundle is 7502 * being loaded. 7503 * 7504 * <li><i>name</i> - Base name of the resource bundle to load. If not specified the default 7505 * base name is "resources". 7506 * 7507 * <li><i>type</i> - Name the type of strings this bundle contains. Valid values are 7508 * "xml", "html", "text", or "raw". The default is "text". If the type is "xml" or "html", 7509 * then XML/HTML entities and tags are not pseudo-translated. During a real translation, 7510 * HTML character entities are translated to their corresponding characters in a source 7511 * string before looking that string up in the translations. Also, the characters "<", ">", 7512 * and "&" are converted to entities again in the output, but characters are left as they 7513 * are. If the type is "xml", "html", or "text" types, then the replacement parameter names 7514 * are not pseudo-translated as well so that the output can be used for formatting with 7515 * the IString class. If the type is raw, all characters are pseudo-translated, 7516 * including replacement parameters as well as XML/HTML tags and entities. 7517 * 7518 * <li><i>lengthen</i> - when pseudo-translating the string, tell whether or not to 7519 * automatically lengthen the string to simulate "long" languages such as German 7520 * or French. This is a boolean value. Default is false. 7521 * 7522 * <li><i>missing</i> - what to do when a resource is missing. The choices are: 7523 * <ul> 7524 * <li><i>source</i> - return the source string unchanged 7525 * <li><i>pseudo</i> - return the pseudo-translated source string, translated to the 7526 * script of the locale if the mapping is available, or just the default Latin 7527 * pseudo-translation if not 7528 * <li><i>empty</i> - return the empty string 7529 * </ul> 7530 * The default behaviour is the same as before, which is to return the source string 7531 * unchanged. 7532 * 7533 * <li><i>onLoad</i> - a callback function to call when the resources are fully 7534 * loaded. When the onLoad option is given, this class will attempt to 7535 * load any missing locale data using the ilib loader callback. 7536 * When the constructor is done (even if the data is already preassembled), the 7537 * onLoad function is called with the current instance as a parameter, so this 7538 * callback can be used with preassembled or dynamic loading or a mix of the two. 7539 * 7540 * <li>sync - tell whether to load any missing locale data synchronously or 7541 * asynchronously. If this option is given as "false", then the "onLoad" 7542 * callback must be given, as the instance returned from this constructor will 7543 * not be usable for a while. 7544 * 7545 * <li><i>loadParams</i> - an object containing parameters to pass to the 7546 * loader callback function when locale data is missing. The parameters are not 7547 * interpretted or modified in any way. They are simply passed along. The object 7548 * may contain any property/value pairs as long as the calling code is in 7549 * agreement with the loader callback function as to what those parameters mean. 7550 * </ul> 7551 * 7552 * The locale option may be given as a locale spec string or as an 7553 * Locale object. If the locale option is not specified, then strings for 7554 * the default locale will be loaded.<p> 7555 * 7556 * The name option can be used to put groups of strings together in a 7557 * single bundle. The strings will then appear together in a JS object in 7558 * a JS file that can be included before the ilib.<p> 7559 * 7560 * A resource bundle with a particular name is actually a set of bundles 7561 * that are each specific to a language, a language plus a region, etc. 7562 * All bundles with the same base name should 7563 * contain the same set of source strings, but with different translations for 7564 * the given locale. The user of the bundle does not need to be aware of 7565 * the locale of the bundle, as long as it contains values for the strings 7566 * it needs.<p> 7567 * 7568 * Strings in bundles for a particular locale are inherited from parent bundles 7569 * that are more generic. In general, the hierarchy is as follows (from 7570 * least locale-specific to most locale-specific): 7571 * 7572 * <ol> 7573 * <li> language 7574 * <li> region 7575 * <li> language_script 7576 * <li> language_region 7577 * <li> region_variant 7578 * <li> language_script_region 7579 * <li> language_region_variant 7580 * <li> language_script_region_variant 7581 * </ol> 7582 * 7583 * That is, if the translation for a string does not exist in the current 7584 * locale, the more-generic parent locale is searched for the string. In the 7585 * worst case scenario, the string is not found in the base locale's strings. 7586 * In this case, the missing option guides this class on what to do. If 7587 * the missing option is "source", then the original source is returned as 7588 * the translation. If it is "empty", the empty string is returned. If it 7589 * is "pseudo", then the pseudo-translated string that is appropriate for 7590 * the default script of the locale is returned.<p> 7591 * 7592 * This allows developers to create code with new or changed strings in it and check in that 7593 * code without waiting for the translations to be done first. The translated 7594 * version of the app or web site will still function properly, but will show 7595 * a spurious untranslated string here and there until the translations are 7596 * done and also checked in.<p> 7597 * 7598 * The base is whatever language your developers use to code in. For 7599 * a German web site, strings in the source code may be written in German 7600 * for example. Often this base is English, as many web sites are coded in 7601 * English, but that is not required.<p> 7602 * 7603 * The strings can be extracted with the ilib localization tool (which will be 7604 * shipped at some future time.) Once the strings 7605 * have been translated, the set of translated files can be generated with the 7606 * same tool. The output from the tool can be used as input to the ResBundle 7607 * object. It is up to the web page or app to make sure the JS file that defines 7608 * the bundle is included before creating the ResBundle instance.<p> 7609 * 7610 * A special locale "zxx-XX" is used as the pseudo-translation locale because 7611 * zxx means "no linguistic information" in the ISO 639 standard, and the region 7612 * code XX is defined to be user-defined in the ISO 3166 standard. 7613 * Pseudo-translation is a locale where the translations are generated on 7614 * the fly based on the contents of the source string. Characters in the source 7615 * string are replaced with other characters and returned. 7616 * 7617 * Example. If the source string is: 7618 * 7619 * <pre> 7620 * "This is a string" 7621 * </pre> 7622 * 7623 * then the pseudo-translated version might look something like this: 7624 * 7625 * <pre> 7626 * "Ţħïş ïş á şţřïñĝ" 7627 * </pre> 7628 * <p> 7629 * 7630 * Pseudo-translation can be used to test that your app or web site is translatable 7631 * before an actual translation has happened. These bugs can then be fixed 7632 * before the translation starts, avoiding an explosion of bugs later when 7633 * each language's tester registers the same bug complaining that the same 7634 * string is not translated. When pseudo-localizing with 7635 * the Latin script, this allows the strings to be readable in the UI in the 7636 * source language (if somewhat funky-looking), 7637 * so that a tester can easily verify that the string is properly externalized 7638 * and loaded from a resource bundle without the need to be able to read a 7639 * foreign language.<p> 7640 * 7641 * If one of a list of script tags is given in the pseudo-locale specifier, then the 7642 * pseudo-localization can map characters to very rough transliterations of 7643 * characters in the given script. For example, zxx-Hebr-XX maps strings to 7644 * Hebrew characters, which can be used to test your UI in a right-to-left 7645 * language to catch bidi bugs before a translation is done. Currently, the 7646 * list of target scripts includes Hebrew (Hebr), Chinese Simplified Han (Hans), 7647 * and Cyrillic (Cyrl) with more to be added later. If no script is explicitly 7648 * specified in the locale spec, or if the script is not supported, 7649 * then the default mapping maps Latin base characters to accented versions of 7650 * those Latin characters as in the example above. 7651 * 7652 * When the "lengthen" property is set to true in the options, the 7653 * pseudotranslation code will add digits to the end of the string to simulate 7654 * the lengthening that occurs when translating to other languages. The above 7655 * example will come out like this: 7656 * 7657 * <pre> 7658 * "Ţħïş ïş á şţřïñĝ76543210" 7659 * </pre> 7660 * 7661 * The string is lengthened according to the length of the source string. If 7662 * the source string is less than 20 characters long, the string is lengthened 7663 * by 50%. If the source string is 20-40 7664 * characters long, the string is lengthened by 33%. If te string is greater 7665 * than 40 characters long, the string is lengthened by 20%.<p> 7666 * 7667 * The pseudotranslation always ends a string with the digit "0". If you do 7668 * not see the digit "0" in the UI for your app, you know that truncation 7669 * has occurred, and the number you see at the end of the string tells you 7670 * how many characters were truncated.<p> 7671 * 7672 * 7673 * @constructor 7674 * @param {?Object} options Options controlling how the bundle is created 7675 */ 7676 var ResBundle = function (options) { 7677 var lookupLocale, spec; 7678 7679 this.locale = new Locale(); // use the default locale 7680 this.baseName = "strings"; 7681 this.type = "text"; 7682 this.loadParams = {}; 7683 this.missing = "source"; 7684 this.sync = true; 7685 7686 if (options) { 7687 if (options.locale) { 7688 this.locale = (typeof(options.locale) === 'string') ? 7689 new Locale(options.locale) : 7690 options.locale; 7691 } 7692 if (options.name) { 7693 this.baseName = options.name; 7694 } 7695 if (options.type) { 7696 this.type = options.type; 7697 } 7698 this.lengthen = options.lengthen || false; 7699 7700 if (typeof(options.sync) !== 'undefined') { 7701 this.sync = (options.sync == true); 7702 } 7703 7704 if (typeof(options.loadParams) !== 'undefined') { 7705 this.loadParams = options.loadParams; 7706 } 7707 if (typeof(options.missing) !== 'undefined') { 7708 if (options.missing === "pseudo" || options.missing === "empty") { 7709 this.missing = options.missing; 7710 } 7711 } 7712 } else { 7713 options = {}; 7714 } 7715 7716 this.map = {}; 7717 7718 if (!ResBundle[this.baseName]) { 7719 ResBundle[this.baseName] = {}; 7720 } 7721 7722 lookupLocale = this.locale.isPseudo() ? new Locale("en-US") : this.locale; 7723 7724 Utils.loadData({ 7725 object: ResBundle[this.baseName], 7726 locale: lookupLocale, 7727 name: this.baseName + ".json", 7728 sync: this.sync, 7729 loadParams: this.loadParams, 7730 callback: ilib.bind(this, function (map) { 7731 if (!map) { 7732 map = ilib.data[this.baseName] || {}; 7733 spec = lookupLocale.getSpec().replace(/-/g, '_'); 7734 ResBundle[this.baseName].cache[spec] = map; 7735 } 7736 this.map = map; 7737 if (this.locale.isPseudo()) { 7738 if (!ResBundle.pseudomap) { 7739 ResBundle.pseudomap = {}; 7740 } 7741 7742 this._loadPseudo(this.locale, options.onLoad); 7743 } else if (this.missing === "pseudo") { 7744 if (!ResBundle.pseudomap) { 7745 ResBundle.pseudomap = {}; 7746 } 7747 7748 new LocaleInfo(this.locale, { 7749 sync: this.sync, 7750 loadParams: this.loadParams, 7751 onLoad: ilib.bind(this, function (li) { 7752 var pseudoLocale = new Locale("zxx", "XX", undefined, li.getDefaultScript()); 7753 this._loadPseudo(pseudoLocale, options.onLoad); 7754 }) 7755 }); 7756 } else { 7757 if (typeof(options.onLoad) === 'function') { 7758 options.onLoad(this); 7759 } 7760 } 7761 }) 7762 }); 7763 7764 // console.log("Merged resources " + this.locale.toString() + " are: " + JSON.stringify(this.map)); 7765 //if (!this.locale.isPseudo() && JSUtils.isEmpty(this.map)) { 7766 // console.log("Resources for bundle " + this.baseName + " locale " + this.locale.toString() + " are not available."); 7767 //} 7768 }; 7769 7770 ResBundle.defaultPseudo = ilib.data.pseudomap || { 7771 "a": "à", 7772 "e": "ë", 7773 "i": "í", 7774 "o": "õ", 7775 "u": "ü", 7776 "y": "ÿ", 7777 "A": "Ã", 7778 "E": "Ë", 7779 "I": "Ï", 7780 "O": "Ø", 7781 "U": "Ú", 7782 "Y": "Ŷ" 7783 }; 7784 7785 ResBundle.prototype = { 7786 /** 7787 * @protected 7788 */ 7789 _loadPseudo: function (pseudoLocale, onLoad) { 7790 Utils.loadData({ 7791 object: ResBundle.pseudomap, 7792 locale: pseudoLocale, 7793 name: "pseudomap.json", 7794 sync: this.sync, 7795 loadParams: this.loadParams, 7796 callback: ilib.bind(this, function (map) { 7797 if (!map || JSUtils.isEmpty(map)) { 7798 map = ResBundle.defaultPseudo; 7799 var spec = pseudoLocale.getSpec().replace(/-/g, '_'); 7800 ResBundle.pseudomap.cache[spec] = map; 7801 } 7802 this.pseudomap = map; 7803 if (typeof(onLoad) === 'function') { 7804 onLoad(this); 7805 } 7806 }) 7807 }); 7808 }, 7809 7810 /** 7811 * Return the locale of this resource bundle. 7812 * @return {Locale} the locale of this resource bundle object 7813 */ 7814 getLocale: function () { 7815 return this.locale; 7816 }, 7817 7818 /** 7819 * Return the name of this resource bundle. This corresponds to the name option 7820 * given to the constructor. 7821 * @return {string} name of the the current instance 7822 */ 7823 getName: function () { 7824 return this.baseName; 7825 }, 7826 7827 /** 7828 * Return the type of this resource bundle. This corresponds to the type option 7829 * given to the constructor. 7830 * @return {string} type of the the current instance 7831 */ 7832 getType: function () { 7833 return this.type; 7834 }, 7835 7836 /** 7837 * @private 7838 * Pseudo-translate a string 7839 */ 7840 _pseudo: function (str) { 7841 if (!str) { 7842 return undefined; 7843 } 7844 var ret = "", i; 7845 for (i = 0; i < str.length; i++) { 7846 if (this.type !== "raw") { 7847 if (this.type === "html" || this.type === "xml") { 7848 if (str.charAt(i) === '<') { 7849 ret += str.charAt(i++); 7850 while (i < str.length && str.charAt(i) !== '>') { 7851 ret += str.charAt(i++); 7852 } 7853 if (i < str.length) { 7854 ret += str.charAt(i++); 7855 } 7856 } else if (str.charAt(i) === '&') { 7857 ret += str.charAt(i++); 7858 while (i < str.length && str.charAt(i) !== ';' && str.charAt(i) !== ' ') { 7859 ret += str.charAt(i++); 7860 } 7861 if (i < str.length) { 7862 ret += str.charAt(i++); 7863 } 7864 } 7865 } 7866 if (i < str.length) { 7867 if (str.charAt(i) === '{') { 7868 ret += str.charAt(i++); 7869 while (i < str.length && str.charAt(i) !== '}') { 7870 ret += str.charAt(i++); 7871 } 7872 if (i < str.length) { 7873 ret += str.charAt(i); 7874 } 7875 } else { 7876 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 7877 } 7878 } 7879 } else { 7880 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 7881 } 7882 } 7883 if (this.lengthen) { 7884 var add; 7885 if (ret.length <= 20) { 7886 add = Math.round(ret.length / 2); 7887 } else if (ret.length > 20 && ret.length <= 40) { 7888 add = Math.round(ret.length / 3); 7889 } else { 7890 add = Math.round(ret.length / 5); 7891 } 7892 for (i = add-1; i >= 0; i--) { 7893 ret += (i % 10); 7894 } 7895 } 7896 if (this.locale.getScript() === "Hans" || this.locale.getScript() === "Hant" || 7897 this.locale.getScript() === "Hani" || 7898 this.locale.getScript() === "Hrkt" || this.locale.getScript() === "Jpan" || 7899 this.locale.getScript() === "Hira" || this.locale.getScript() === "Kana" ) { 7900 // simulate Asian languages by getting rid of all the spaces 7901 ret = ret.replace(/ /g, ""); 7902 } 7903 return ret; 7904 }, 7905 7906 /** 7907 * @private 7908 * Escape html characters in the output. 7909 */ 7910 _escapeXml: function (str) { 7911 str = str.replace(/&/g, '&'); 7912 str = str.replace(/</g, '<'); 7913 str = str.replace(/>/g, '>'); 7914 return str; 7915 }, 7916 7917 /** 7918 * @private 7919 * @param {string} str the string to unescape 7920 */ 7921 _unescapeXml: function (str) { 7922 str = str.replace(/&/g, '&'); 7923 str = str.replace(/</g, '<'); 7924 str = str.replace(/>/g, '>'); 7925 return str; 7926 }, 7927 7928 /** 7929 * @private 7930 * Create a key name out of a source string. All this does so far is 7931 * compress sequences of white space into a single space on the assumption 7932 * that this doesn't really change the meaning of the string, and therefore 7933 * all such strings that compress to the same thing should share the same 7934 * translation. 7935 * @param {null|string=} source the source string to make a key out of 7936 */ 7937 _makeKey: function (source) { 7938 if (!source) return undefined; 7939 var key = source.replace(/\s+/gm, ' '); 7940 return (this.type === "xml" || this.type === "html") ? this._unescapeXml(key) : key; 7941 }, 7942 7943 /** 7944 * Return a localized string. If the string is not found in the loaded set of 7945 * resources, the original source string is returned. If the key is not given, 7946 * then the source string itself is used as the key. In the case where the 7947 * source string is used as the key, the whitespace is compressed down to 1 space 7948 * each, and the whitespace at the beginning and end of the string is trimmed.<p> 7949 * 7950 * The escape mode specifies what type of output you are escaping the returned 7951 * string for. Modes are similar to the types: 7952 * 7953 * <ul> 7954 * <li>"html" -- prevents HTML injection by escaping the characters < > and & 7955 * <li>"xml" -- currently same as "html" mode 7956 * <li>"js" -- prevents breaking Javascript syntax by backslash escaping all quote and 7957 * double-quote characters 7958 * <li>"attribute" -- meant for HTML attribute values. Currently this is the same as 7959 * "js" escape mode. 7960 * <li>"default" -- use the type parameter from the constructor as the escape mode as well 7961 * <li>"none" or undefined -- no escaping at all. 7962 * </ul> 7963 * 7964 * The type parameter of the constructor specifies what type of strings this bundle 7965 * is operating upon. This allows pseudo-translation and automatic key generation 7966 * to happen properly by telling this class how to parse the string. The escape mode 7967 * for this method is different in that it specifies how this string will be used in 7968 * the calling code and therefore how to escape it properly.<p> 7969 * 7970 * For example, a section of Javascript code may be constructing an HTML snippet in a 7971 * string to add to the web page. In this case, the type parameter in the constructor should 7972 * be "html" so that the source string can be parsed properly, but the escape mode should 7973 * be "js" so that the output string can be used in Javascript without causing syntax 7974 * errors. 7975 * 7976 * @param {?string=} source the source string to translate 7977 * @param {?string=} key optional name of the key, if any 7978 * @param {?string=} escapeMode escape mode, if any 7979 * @return {IString|undefined} the translation of the given source/key or undefined 7980 * if the translation is not found and the source is undefined 7981 */ 7982 getString: function (source, key, escapeMode) { 7983 if (!source && !key) return new IString(""); 7984 7985 var trans; 7986 if (this.locale.isPseudo()) { 7987 var str = source ? source : this.map[key]; 7988 trans = this._pseudo(str || key); 7989 } else { 7990 var keyName = key || this._makeKey(source); 7991 if (typeof(this.map[keyName]) !== 'undefined') { 7992 trans = this.map[keyName]; 7993 } else if (this.missing === "pseudo") { 7994 trans = this._pseudo(source || key); 7995 } else if (this.missing === "empty") { 7996 trans = ""; 7997 } else { 7998 trans = source; 7999 } 8000 } 8001 8002 if (escapeMode && escapeMode !== "none") { 8003 if (escapeMode == "default") { 8004 escapeMode = this.type; 8005 } 8006 if (escapeMode === "xml" || escapeMode === "html") { 8007 trans = this._escapeXml(trans); 8008 } else if (escapeMode == "js" || escapeMode === "attribute") { 8009 trans = trans.replace(/'/g, "\\\'").replace(/"/g, "\\\""); 8010 } 8011 } 8012 if (trans === undefined) { 8013 return undefined; 8014 } else { 8015 var ret = new IString(trans); 8016 ret.setLocale(this.locale.getSpec(), true, this.loadParams); // no callback 8017 return ret; 8018 } 8019 }, 8020 8021 /** 8022 * Return a localized string as a Javascript object. This does the same thing as 8023 * the getString() method, but it returns a regular Javascript string instead of 8024 * and IString instance. This means it cannot be formatted with the format() 8025 * method without being wrapped in an IString instance first. 8026 * 8027 * @param {?string=} source the source string to translate 8028 * @param {?string=} key optional name of the key, if any 8029 * @param {?string=} escapeMode escape mode, if any 8030 * @return {string|undefined} the translation of the given source/key or undefined 8031 * if the translation is not found and the source is undefined 8032 */ 8033 getStringJS: function(source, key, escapeMode) { 8034 return this.getString(source, key, escapeMode).toString(); 8035 }, 8036 8037 /** 8038 * Return true if the current bundle contains a translation for the given key and 8039 * source. The 8040 * getString method will always return a string for any given key and source 8041 * combination, so it cannot be used to tell if a translation exists. Either one 8042 * or both of the source and key must be specified. If both are not specified, 8043 * this method will return false. 8044 * 8045 * @param {?string=} source source string to look up 8046 * @param {?string=} key key to look up 8047 * @return {boolean} true if this bundle contains a translation for the key, and 8048 * false otherwise 8049 */ 8050 containsKey: function(source, key) { 8051 if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { 8052 return false; 8053 } 8054 8055 var keyName = key || this._makeKey(source); 8056 return typeof(this.map[keyName]) !== 'undefined'; 8057 }, 8058 8059 /** 8060 * Return the merged resources as an entire object. When loading resources for a 8061 * locale that are not just a set of translated strings, but instead an entire 8062 * structured javascript object, you can gain access to that object via this call. This method 8063 * will ensure that all the of the parts of the object are correct for the locale.<p> 8064 * 8065 * For pre-assembled data, it starts by loading <i>ilib.data[name]</i>, where 8066 * <i>name</i> is the base name for this set of resources. Then, it successively 8067 * merges objects in the base data using progressively more locale-specific data. 8068 * It loads it in this order from <i>ilib.data</i>: 8069 * 8070 * <ol> 8071 * <li> language 8072 * <li> region 8073 * <li> language_script 8074 * <li> language_region 8075 * <li> region_variant 8076 * <li> language_script_region 8077 * <li> language_region_variant 8078 * <li> language_script_region_variant 8079 * </ol> 8080 * 8081 * For dynamically loaded data, the code attempts to load the same sequence as 8082 * above, but with slash path separators instead of underscores.<p> 8083 * 8084 * Loading the resources this way allows the program to share resources between all 8085 * locales that share a common language, region, or script. As a 8086 * general rule-of-thumb, resources should be as generic as possible in order to 8087 * cover as many locales as possible. 8088 * 8089 * @return {Object} returns the object that is the basis for this resources instance 8090 */ 8091 getResObj: function () { 8092 return this.map; 8093 } 8094 }; 8095 8096 8097 /*< ISet.js */ 8098 /* 8099 * ISet.js - ilib Set class definition for platforms older than ES6 8100 * 8101 * Copyright © 2015, JEDLSoft 8102 * 8103 * Licensed under the Apache License, Version 2.0 (the "License"); 8104 * you may not use this file except in compliance with the License. 8105 * You may obtain a copy of the License at 8106 * 8107 * http://www.apache.org/licenses/LICENSE-2.0 8108 * 8109 * Unless required by applicable law or agreed to in writing, software 8110 * distributed under the License is distributed on an "AS IS" BASIS, 8111 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8112 * 8113 * See the License for the specific language governing permissions and 8114 * limitations under the License. 8115 */ 8116 8117 /** 8118 * Create a new set with elements in the given array. The type of 8119 * the set is gleaned from the type of the first element in the 8120 * elements array, or the first element added to the set. The type 8121 * may be "string" or "number", and all elements will be returned 8122 * as elements of that type. 8123 * 8124 * @class 8125 * @param {Array.<string|number>} elements initial elements to add to the set 8126 * @constructor 8127 */ 8128 var ISet = function(elements) { 8129 this.elements = {}; 8130 8131 if (elements && elements.length) { 8132 for (var i = 0; i < elements.length; i++) { 8133 this.elements[elements[i]] = true; 8134 } 8135 8136 this.type = typeof(elements[0]); 8137 } 8138 }; 8139 8140 /** 8141 * @private 8142 */ 8143 ISet.prototype._addOne = function(element) { 8144 if (this.isEmpty()) { 8145 this.type = typeof(element); 8146 } 8147 8148 if (!this.elements[element]) { 8149 this.elements[element] = true; 8150 return true; 8151 } 8152 8153 return false; 8154 }; 8155 8156 /** 8157 * Adds the specified element or array of elements to this set if it is or they are not 8158 * already present. 8159 * 8160 * @param {*|Array.<*>} element element or array of elements to add 8161 * @return {boolean} true if this set did not already contain the specified element[s] 8162 */ 8163 ISet.prototype.add = function(element) { 8164 var ret = false; 8165 8166 if (typeof(element) === "object") { 8167 for (var i = 0; i < element.length; i++) { 8168 ret = this._addOne(element[i]) || ret; 8169 } 8170 } else { 8171 ret = this._addOne(element); 8172 } 8173 8174 return ret; 8175 }; 8176 8177 /** 8178 * Removes all of the elements from this set. 8179 */ 8180 ISet.prototype.clear = function() { 8181 this.elements = {}; 8182 }; 8183 8184 /** 8185 * Returns true if this set contains the specified element. 8186 * @param {*} element the element to test 8187 * @return {boolean} 8188 */ 8189 ISet.prototype.contains = function(element) { 8190 return this.elements[element] || false; 8191 }; 8192 8193 /** 8194 * Returns true if this set contains no elements. 8195 * @return {boolean} 8196 */ 8197 ISet.prototype.isEmpty = function() { 8198 return (Object.keys(this.elements).length === 0); 8199 }; 8200 8201 /** 8202 * Removes the specified element from this set if it is present. 8203 * @param {*} element the element to remove 8204 * @return {boolean} true if the set contained the specified element 8205 */ 8206 ISet.prototype.remove = function(element) { 8207 if (this.elements[element]) { 8208 delete this.elements[element]; 8209 return true; 8210 } 8211 8212 return false; 8213 }; 8214 8215 /** 8216 * Return the set as a javascript array. 8217 * @return {Array.<*>} the set represented as a javascript array 8218 */ 8219 ISet.prototype.asArray = function() { 8220 var keys = Object.keys(this.elements); 8221 8222 // keys is an array of strings. Convert to numbers if necessary 8223 if (this.type === "number") { 8224 var tmp = []; 8225 for (var i = 0; i < keys.length; i++) { 8226 tmp.push(Number(keys[i]).valueOf()); 8227 } 8228 keys = tmp; 8229 } 8230 8231 return keys; 8232 }; 8233 8234 /** 8235 * Represents the current set as json. 8236 * @return {string} the current set represented as json 8237 */ 8238 ISet.prototype.toJson = function() { 8239 return JSON.stringify(this.asArray()); 8240 }; 8241 8242 /** 8243 * Convert to a javascript representation of this object. 8244 * In this case, it is a normal JS array. 8245 * @return {*} the JS representation of this object 8246 */ 8247 ISet.prototype.toJS = function() { 8248 return this.asArray(); 8249 }; 8250 8251 /** 8252 * Convert from a js representation to an internal one. 8253 * @return {ISet|undefined} the current object, or undefined if the conversion did not work 8254 */ 8255 ISet.prototype.fromJS = function(obj) { 8256 return this.add(obj) ? this : undefined; 8257 }; 8258 8259 8260 8261 /*< DateFmt.js */ 8262 /* 8263 * DateFmt.js - Date formatter definition 8264 * 8265 * Copyright © 2012-2015, JEDLSoft 8266 * 8267 * Licensed under the Apache License, Version 2.0 (the "License"); 8268 * you may not use this file except in compliance with the License. 8269 * You may obtain a copy of the License at 8270 * 8271 * http://www.apache.org/licenses/LICENSE-2.0 8272 * 8273 * Unless required by applicable law or agreed to in writing, software 8274 * distributed under the License is distributed on an "AS IS" BASIS, 8275 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8276 * 8277 * See the License for the specific language governing permissions and 8278 * limitations under the License. 8279 */ 8280 8281 /* 8282 !depends 8283 ilib.js 8284 Locale.js 8285 IDate.js 8286 DateFactory.js 8287 IString.js 8288 ResBundle.js 8289 Calendar.js 8290 CalendarFactory.js 8291 LocaleInfo.js 8292 TimeZone.js 8293 GregorianCal.js 8294 JSUtils.js 8295 Utils.js 8296 ISet.js 8297 */ 8298 8299 // !data dateformats sysres 8300 8301 8302 8303 8304 8305 8306 /** 8307 * @class 8308 * Create a new date formatter instance. The date formatter is immutable once 8309 * it is created, but can format as many different dates as needed with the same 8310 * options. Create different date formatter instances for different purposes 8311 * and then keep them cached for use later if you have more than one date to 8312 * format.<p> 8313 * 8314 * The options may contain any of the following properties: 8315 * 8316 * <ul> 8317 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8318 * not specified, then the default locale of the app or web page will be used. 8319 * 8320 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 8321 * be a sting containing the name of the calendar. Currently, the supported 8322 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 8323 * calendar is not specified, then the default calendar for the locale is used. When the 8324 * calendar type is specified, then the format method must be called with an instance of 8325 * the appropriate date type. (eg. Gregorian calendar means that the format method must 8326 * be called with a GregDate instance.) 8327 * 8328 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 8329 * instance or a time zone specifier from the IANA list of time zone database names 8330 * (eg. "America/Los_Angeles"), 8331 * the string "local", or a string specifying the offset in RFC 822 format. The IANA 8332 * list of time zone names can be viewed at 8333 * <a href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">this page</a>. 8334 * If the time zone is given as "local", the offset from UTC as given by 8335 * the Javascript system is used. If the offset is given as an RFC 822 style offset 8336 * specifier, it will parse that string and use the resulting offset. If the time zone 8337 * is not specified, the 8338 * default time zone for the locale is used. If both the date object and this formatter 8339 * instance contain time zones and those time zones are different from each other, the 8340 * formatter will calculate the offset between the time zones and subtract it from the 8341 * date before formatting the result for the current time zone. The theory is that a date 8342 * object that contains a time zone specifies a specific instant in time that is valid 8343 * around the world, whereas a date object without one is a local time and can only be 8344 * used for doing things in the local time zone of the user. 8345 * 8346 * <li><i>type</i> - Specify whether this formatter should format times only, dates only, or 8347 * both times and dates together. Valid values are "time", "date", and "datetime". Note that 8348 * in some locales, the standard format uses the order "time followed by date" and in others, 8349 * the order is exactly opposite, so it is better to create a single "datetime" formatter 8350 * than it is to create a time formatter and a date formatter separately and concatenate the 8351 * results. A "datetime" formatter will get the order correct for the locale.<p> 8352 * 8353 * The default type if none is specified in with the type option is "date". 8354 * 8355 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 8356 * formatted string. 8357 * 8358 * <ul> 8359 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 8360 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 8361 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 8362 * components may still be abbreviated 8363 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 8364 * components are spelled out completely 8365 * </ul> 8366 * 8367 * eg. The "short" format for an en_US date may be "MM/dd/yy", whereas the long format might be "d MMM, yyyy". In the long 8368 * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format 8369 * contains slightly more spaces and formatting characters.<p> 8370 * 8371 * Note that the length parameter does not specify which components are to be formatted. Use the "date" and the "time" 8372 * properties to specify the components. Also, very few of the components of a time format differ according to the length, 8373 * so this property has little to no affect on time formatting. 8374 * 8375 * <li><i>date</i> - This property tells 8376 * which components of a date format to use. For example, 8377 * sometimes you may wish to format a date that only contains the month and date 8378 * without the year, such as when displaying a person's yearly birthday. The value 8379 * of this property allows you to specify only those components you want to see in the 8380 * final output, ordered correctly for the locale. <p> 8381 * 8382 * Valid values are: 8383 * 8384 * <ul> 8385 * <li><i>dmwy</i> - format all components, weekday, date, month, and year 8386 * <li><i>dmy</i> - format only date, month, and year 8387 * <li><i>dmw</i> - format only weekday, date, and month 8388 * <li><i>dm</i> - format only date and month 8389 * <li><i>my</i> - format only month and year 8390 * <li><i>dw</i> - format only the weekday and date 8391 * <li><i>d</i> - format only the date 8392 * <li><i>m</i> - format only the month, in numbers for shorter lengths, and letters for 8393 * longer lengths 8394 * <li><i>n</i> - format only the month, in letters only for all lengths 8395 * <li><i>y</i> - format only the year 8396 * </ul> 8397 * Default components, if this property is not specified, is "dmy". This property may be specified 8398 * but has no affect if the current formatter is for times only.<p> 8399 * 8400 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 8401 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 8402 * It will not extract the length from the skeleton so you still need to pass the length property, 8403 * but it will extract the date components. 8404 * 8405 * <li><i>time</i> - This property gives which components of a time format to use. The time will be formatted 8406 * correctly for the locale with only the time components requested. For example, a clock might only display 8407 * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set 8408 * to "hm". <p> 8409 * 8410 * Valid values for this property are: 8411 * 8412 * <ul> 8413 * <li><i>ahmsz</i> - format the hours, minutes, seconds, am/pm (if using a 12 hour clock), and the time zone 8414 * <li><i>ahms</i> - format the hours, minutes, seconds, and am/pm (if using a 12 hour clock) 8415 * <li><i>hmsz</i> - format the hours, minutes, seconds, and the time zone 8416 * <li><i>hms</i> - format the hours, minutes, and seconds 8417 * <li><i>ahmz</i> - format the hours, minutes, am/pm (if using a 12 hour clock), and the time zone 8418 * <li><i>ahm</i> - format the hours, minutes, and am/pm (if using a 12 hour clock) 8419 * <li><i>hmz</i> - format the hours, minutes, and the time zone 8420 * <li><i>ah</i> - format only the hours and am/pm if using a 12 hour clock 8421 * <li><i>hm</i> - format only the hours and minutes 8422 * <li><i>ms</i> - format only the minutes and seconds 8423 * <li><i>h</i> - format only the hours 8424 * <li><i>m</i> - format only the minutes 8425 * <li><i>s</i> - format only the seconds 8426 * </ul> 8427 * 8428 * If you want to format a length of time instead of a particular instant 8429 * in time, use the duration formatter object (DurationFmt) instead because this 8430 * formatter is geared towards instants. A date formatter will make sure that each component of the 8431 * time is within the normal range 8432 * for that component. That is, the minutes will always be between 0 and 59, no matter 8433 * what is specified in the date to format. A duration format will allow the number 8434 * of minutes to exceed 59 if, for example, you were displaying the length of 8435 * a movie of 198 minutes.<p> 8436 * 8437 * Default value if this property is not specified is "hma".<p> 8438 * 8439 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 8440 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 8441 * It will not extract the length from the skeleton so you still need to pass the length property, 8442 * but it will extract the time components. 8443 * 8444 * <li><i>clock</i> - specify that the time formatter should use a 12 or 24 hour clock. 8445 * Valid values are "12" and "24".<p> 8446 * 8447 * In some locales, both clocks are used. For example, in en_US, the general populace uses 8448 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 8449 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 8450 * construct a formatter that overrides the default for the locale.<p> 8451 * 8452 * If this property is not specified, the default is to use the most widely used convention 8453 * for the locale. 8454 * 8455 * <li><i>template</i> - use the given template string as a fixed format when formatting 8456 * the date/time. Valid codes to use in a template string are as follows: 8457 * 8458 * <ul> 8459 * <li><i>a</i> - am/pm marker 8460 * <li><i>d</i> - 1 or 2 digit date of month, not padded 8461 * <li><i>dd</i> - 1 or 2 digit date of month, 0 padded to 2 digits 8462 * <li><i>O</i> - ordinal representation of the date of month (eg. "1st", "2nd", etc.) 8463 * <li><i>D</i> - 1 to 3 digit day of year 8464 * <li><i>DD</i> - 1 to 3 digit day of year, 0 padded to 2 digits 8465 * <li><i>DDD</i> - 1 to 3 digit day of year, 0 padded to 3 digits 8466 * <li><i>M</i> - 1 or 2 digit month number, not padded 8467 * <li><i>MM</i> - 1 or 2 digit month number, 0 padded to 2 digits 8468 * <li><i>N</i> - 1 character month name abbreviation 8469 * <li><i>NN</i> - 2 character month name abbreviation 8470 * <li><i>MMM</i> - 3 character month month name abbreviation 8471 * <li><i>MMMM</i> - fully spelled out month name 8472 * <li><i>yy</i> - 2 digit year 8473 * <li><i>yyyy</i> - 4 digit year 8474 * <li><i>E</i> - day-of-week name, abbreviated to a single character 8475 * <li><i>EE</i> - day-of-week name, abbreviated to a max of 2 characters 8476 * <li><i>EEE</i> - day-of-week name, abbreviated to a max of 3 characters 8477 * <li><i>EEEE</i> - day-of-week name fully spelled out 8478 * <li><i>G</i> - era designator 8479 * <li><i>w</i> - week number in year 8480 * <li><i>ww</i> - week number in year, 0 padded to 2 digits 8481 * <li><i>W</i> - week in month 8482 * <li><i>h</i> - hour (12 followed by 1 to 11) 8483 * <li><i>hh</i> - hour (12, followed by 1 to 11), 0 padded to 2 digits 8484 * <li><i>k</i> - hour (1 to 24) 8485 * <li><i>kk</i> - hour (1 to 24), 0 padded to 2 digits 8486 * <li><i>H</i> - hour (0 to 23) 8487 * <li><i>HH</i> - hour (0 to 23), 0 padded to 2 digits 8488 * <li><i>K</i> - hour (0 to 11) 8489 * <li><i>KK</i> - hour (0 to 11), 0 padded to 2 digits 8490 * <li><i>m</i> - minute in hour 8491 * <li><i>mm</i> - minute in hour, 0 padded to 2 digits 8492 * <li><i>s</i> - second in minute 8493 * <li><i>ss</i> - second in minute, 0 padded to 2 digits 8494 * <li><i>S</i> - millisecond (1 to 3 digits) 8495 * <li><i>SSS</i> - millisecond, 0 padded to 3 digits 8496 * <li><i>z</i> - general time zone 8497 * <li><i>Z</i> - RFC 822 time zone 8498 * </ul> 8499 * 8500 * <li><i>useNative</i> - the flag used to determine whether to use the native script settings 8501 * for formatting the numbers. 8502 * 8503 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8504 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8505 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8506 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8507 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8508 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8509 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8510 * when formatting dates in the Gregorian calendar. 8511 * 8512 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 8513 * loaded. When the onLoad option is given, the DateFmt object will attempt to 8514 * load any missing locale data using the ilib loader callback. 8515 * When the constructor is done (even if the data is already preassembled), the 8516 * onLoad function is called with the current instance as a parameter, so this 8517 * callback can be used with preassembled or dynamic loading or a mix of the two. 8518 * 8519 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 8520 * asynchronously. If this option is given as "false", then the "onLoad" 8521 * callback must be given, as the instance returned from this constructor will 8522 * not be usable for a while. 8523 * 8524 * <li><i>loadParams</i> - an object containing parameters to pass to the 8525 * loader callback function when locale data is missing. The parameters are not 8526 * interpretted or modified in any way. They are simply passed along. The object 8527 * may contain any property/value pairs as long as the calling code is in 8528 * agreement with the loader callback function as to what those parameters mean. 8529 * </ul> 8530 * 8531 * Any substring containing letters within single or double quotes will be used 8532 * as-is in the final output and will not be interpretted for codes as above.<p> 8533 * 8534 * Example: a date format in Spanish might be given as: "'El' d. 'de' MMMM", where 8535 * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical 8536 * output for this example template might be, "El 5. de Mayo". 8537 * 8538 * The following options will be used when formatting a date/time with an explicit 8539 * template: 8540 * 8541 * <ul> 8542 * <li>locale - the locale is only used for 8543 * translations of things like month names or day-of-week names. 8544 * <li>calendar - used to translate a date instance into date/time component values 8545 * that can be formatted into the template 8546 * <li>timezone - used to figure out the offset to add or subtract from the time to 8547 * get the final time component values 8548 * <li>clock - used to figure out whether to format times with a 12 or 24 hour clock. 8549 * If this option is specified, it will override the hours portion of a time format. 8550 * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. 8551 * If this option is not specified, the 12/24 code in the template will dictate whether 8552 * to use the 12 or 24 clock, and the 12/24 default in the locale will be ignored. 8553 * </ul> 8554 * 8555 * All other options will be ignored and their corresponding getter methods will 8556 * return the empty string.<p> 8557 * 8558 * 8559 * @constructor 8560 * @param {Object} options options governing the way this date formatter instance works 8561 */ 8562 var DateFmt = function(options) { 8563 var arr, i, bad, 8564 sync = true, 8565 loadParams = undefined; 8566 8567 this.locale = new Locale(); 8568 this.type = "date"; 8569 this.length = "s"; 8570 this.dateComponents = "dmy"; 8571 this.timeComponents = "ahm"; 8572 this.meridiems = "default"; 8573 8574 if (options) { 8575 if (options.locale) { 8576 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 8577 } 8578 8579 if (options.type) { 8580 if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { 8581 this.type = options.type; 8582 } 8583 } 8584 8585 if (options.calendar) { 8586 this.calName = options.calendar; 8587 } 8588 8589 if (options.length) { 8590 if (options.length === 'short' || 8591 options.length === 'medium' || 8592 options.length === 'long' || 8593 options.length === 'full') { 8594 // only use the first char to save space in the json files 8595 this.length = options.length.charAt(0); 8596 } 8597 } 8598 8599 if (options.date) { 8600 arr = options.date.split(""); 8601 var dateComps = new ISet(); 8602 bad = false; 8603 for (i = 0; i < arr.length; i++) { 8604 var c = arr[i].toLowerCase(); 8605 if (c === "e") c = "w"; // map ICU -> ilib 8606 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'n') { 8607 // ignore time components and the era 8608 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z' && c !== 'g') { 8609 bad = true; 8610 break; 8611 } 8612 } else { 8613 dateComps.add(c); 8614 } 8615 } 8616 if (!bad) { 8617 var comps = dateComps.asArray().sort(function (left, right) { 8618 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8619 }); 8620 this.dateComponents = comps.join(""); 8621 } 8622 } 8623 8624 if (options.time) { 8625 arr = options.time.split(""); 8626 var timeComps = new ISet(); 8627 this.badTime = false; 8628 for (i = 0; i < arr.length; i++) { 8629 var c = arr[i].toLowerCase(); 8630 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z') { 8631 // ignore the date components 8632 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'e' && c !== 'n' && c !== 'g') { 8633 this.badTime = true; 8634 break; 8635 } 8636 } else { 8637 timeComps.add(c); 8638 } 8639 } 8640 if (!this.badTime) { 8641 var comps = timeComps.asArray().sort(function (left, right) { 8642 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8643 }); 8644 this.timeComponents = comps.join(""); 8645 } 8646 } 8647 8648 if (options.clock && (options.clock === '12' || options.clock === '24')) { 8649 this.clock = options.clock; 8650 } 8651 8652 if (options.template) { 8653 // many options are not useful when specifying the template directly, so zero 8654 // them out. 8655 this.type = ""; 8656 this.length = ""; 8657 this.dateComponents = ""; 8658 this.timeComponents = ""; 8659 8660 this.template = options.template; 8661 } 8662 8663 if (options.timezone) { 8664 if (options.timezone instanceof TimeZone) { 8665 this.tz = options.timezone; 8666 } else { 8667 this.tz = new TimeZone({ 8668 locale: this.locale, 8669 id: options.timezone 8670 }); 8671 } 8672 } else if (options.locale) { 8673 // if an explicit locale was given, then get the time zone for that locale 8674 this.tz = new TimeZone({ 8675 locale: this.locale 8676 }); 8677 } // else just assume time zone "local" 8678 8679 if (typeof(options.useNative) === 'boolean') { 8680 this.useNative = options.useNative; 8681 } 8682 8683 if (typeof(options.meridiems) !== 'undefined' && 8684 (options.meridiems === "chinese" || 8685 options.meridiems === "gregorian" || 8686 options.meridiems === "ethiopic")) { 8687 this.meridiems = options.meridiems; 8688 } 8689 8690 if (typeof(options.sync) !== 'undefined') { 8691 sync = (options.sync === true); 8692 } 8693 8694 loadParams = options.loadParams; 8695 } 8696 8697 if (!DateFmt.cache) { 8698 DateFmt.cache = {}; 8699 } 8700 8701 new LocaleInfo(this.locale, { 8702 sync: sync, 8703 loadParams: loadParams, 8704 onLoad: ilib.bind(this, function (li) { 8705 this.locinfo = li; 8706 8707 // get the default calendar name from the locale, and if the locale doesn't define 8708 // one, use the hard-coded gregorian as the last resort 8709 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 8710 if (ilib.isDynCode()) { 8711 // If we are running in the dynamic code loading assembly of ilib, the following 8712 // will attempt to dynamically load the calendar date class for this calendar. If 8713 // it doesn't work, this just goes on and it will use Gregorian instead. 8714 DateFactory._dynLoadDate(this.calName); 8715 } 8716 8717 this.cal = CalendarFactory({ 8718 type: this.calName 8719 }); 8720 if (!this.cal) { 8721 this.cal = new GregorianCal(); 8722 } 8723 if (this.meridiems === "default") { 8724 this.meridiems = li.getMeridiemsStyle(); 8725 } 8726 8727 /* 8728 if (this.timeComponents && 8729 (this.clock === '24' || 8730 (!this.clock && this.locinfo.getClock() === "24"))) { 8731 // make sure we don't have am/pm in 24 hour mode unless the user specifically 8732 // requested it in the time component option 8733 this.timeComponents = this.timeComponents.replace("a", ""); 8734 } 8735 */ 8736 8737 // load the strings used to translate the components 8738 new ResBundle({ 8739 locale: this.locale, 8740 name: "sysres", 8741 sync: sync, 8742 loadParams: loadParams, 8743 onLoad: ilib.bind(this, function (rb) { 8744 this.sysres = rb; 8745 8746 if (!this.template) { 8747 Utils.loadData({ 8748 object: DateFmt, 8749 locale: this.locale, 8750 name: "dateformats.json", 8751 sync: sync, 8752 loadParams: loadParams, 8753 callback: ilib.bind(this, function (formats) { 8754 if (!formats) { 8755 formats = ilib.data.dateformats || DateFmt.defaultFmt; 8756 var spec = this.locale.getSpec().replace(/-/g, '_'); 8757 DateFmt.cache[spec] = formats; 8758 } 8759 if (typeof(this.clock) === 'undefined') { 8760 // default to the locale instead 8761 this.clock = this.locinfo.getClock(); 8762 } 8763 this._initTemplate(formats); 8764 this._massageTemplate(); 8765 if (options && typeof(options.onLoad) === 'function') { 8766 options.onLoad(this); 8767 } 8768 }) 8769 }); 8770 } else { 8771 this._massageTemplate(); 8772 if (options && typeof(options.onLoad) === 'function') { 8773 options.onLoad(this); 8774 } 8775 } 8776 }) 8777 }); 8778 }) 8779 }); 8780 }; 8781 8782 // used in getLength 8783 DateFmt.lenmap = { 8784 "s": "short", 8785 "m": "medium", 8786 "l": "long", 8787 "f": "full" 8788 }; 8789 8790 DateFmt.defaultFmt = { 8791 "gregorian": { 8792 "order": "{date} {time}", 8793 "date": { 8794 "dmwy": "EEE d/MM/yyyy", 8795 "dmy": "d/MM/yyyy", 8796 "dmw": "EEE d/MM", 8797 "dm": "d/MM", 8798 "my": "MM/yyyy", 8799 "dw": "EEE d", 8800 "d": "dd", 8801 "m": "MM", 8802 "y": "yyyy", 8803 "n": "NN", 8804 "w": "EEE" 8805 }, 8806 "time": { 8807 "12": "h:mm:ssa", 8808 "24": "H:mm:ss" 8809 }, 8810 "range": { 8811 "c00": "{st} - {et}, {sd}/{sm}/{sy}", 8812 "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8813 "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8814 "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", 8815 "c10": "{sd}-{ed}/{sm}/{sy}", 8816 "c11": "{sd}/{sm} - {ed}/{em} {sy}", 8817 "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", 8818 "c20": "{sm}/{sy} - {em}/{ey}", 8819 "c30": "{sy} - {ey}" 8820 } 8821 }, 8822 "islamic": "gregorian", 8823 "hebrew": "gregorian", 8824 "julian": "gregorian", 8825 "buddhist": "gregorian", 8826 "persian": "gregorian", 8827 "persian-algo": "gregorian", 8828 "han": "gregorian" 8829 }; 8830 8831 /** 8832 * @static 8833 * @private 8834 */ 8835 DateFmt.monthNameLenMap = { 8836 "short" : "N", 8837 "medium": "NN", 8838 "long": "MMM", 8839 "full": "MMMM" 8840 }; 8841 8842 /** 8843 * @static 8844 * @private 8845 */ 8846 DateFmt.weekDayLenMap = { 8847 "short" : "E", 8848 "medium": "EE", 8849 "long": "EEE", 8850 "full": "EEEE" 8851 }; 8852 8853 /** 8854 * Return the range of possible meridiems (times of day like "AM" or 8855 * "PM") in this date formatter.<p> 8856 * 8857 * The options may contain any of the following properties: 8858 * 8859 * <ul> 8860 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8861 * not specified, then the default locale of the app or web page will be used. 8862 * 8863 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8864 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8865 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8866 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8867 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8868 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8869 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8870 * when formatting dates in the Gregorian calendar. 8871 * </ul> 8872 * 8873 * @static 8874 * @public 8875 * @param {Object} options options governing the way this date formatter instance works for getting meridiems range 8876 * @return {Array.<{name:string,start:string,end:string}>} 8877 */ 8878 DateFmt.getMeridiemsRange = function (options) { 8879 options = options || {}; 8880 var args = {}; 8881 if (options.locale) { 8882 args.locale = options.locale; 8883 } 8884 8885 if (options.meridiems) { 8886 args.meridiems = options.meridiems; 8887 } 8888 8889 var fmt = new DateFmt(args); 8890 8891 return fmt.getMeridiemsRange(); 8892 }; 8893 8894 DateFmt.prototype = { 8895 /** 8896 * @protected 8897 * @param {string|{ 8898 * order:(string|{ 8899 * s:string, 8900 * m:string, 8901 * l:string, 8902 * f:string 8903 * }), 8904 * date:Object.<string, (string|{ 8905 * s:string, 8906 * m:string, 8907 * l:string, 8908 * f:string 8909 * })>, 8910 * time:Object.<string,Object.<string,(string|{ 8911 * s:string, 8912 * m:string, 8913 * l:string, 8914 * f:string 8915 * })>>, 8916 * range:Object.<string, (string|{ 8917 * s:string, 8918 * m:string, 8919 * l:string, 8920 * f:string 8921 * })> 8922 * }} formats 8923 */ 8924 _initTemplate: function (formats) { 8925 if (formats[this.calName]) { 8926 var name = formats[this.calName]; 8927 // may be an alias to another calendar type 8928 this.formats = (typeof(name) === "string") ? formats[name] : name; 8929 8930 this.template = ""; 8931 8932 switch (this.type) { 8933 case "datetime": 8934 this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; 8935 this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); 8936 this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); 8937 break; 8938 case "date": 8939 this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); 8940 break; 8941 case "time": 8942 this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); 8943 break; 8944 } 8945 } else { 8946 throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); 8947 } 8948 }, 8949 8950 /** 8951 * @protected 8952 */ 8953 _massageTemplate: function () { 8954 var i; 8955 8956 if (this.clock && this.template) { 8957 // explicitly set the hours to the requested type 8958 var temp = ""; 8959 switch (this.clock) { 8960 case "24": 8961 for (i = 0; i < this.template.length; i++) { 8962 if (this.template.charAt(i) == "'") { 8963 temp += this.template.charAt(i++); 8964 while (i < this.template.length && this.template.charAt(i) !== "'") { 8965 temp += this.template.charAt(i++); 8966 } 8967 if (i < this.template.length) { 8968 temp += this.template.charAt(i); 8969 } 8970 } else if (this.template.charAt(i) == 'K') { 8971 temp += 'k'; 8972 } else if (this.template.charAt(i) == 'h') { 8973 temp += 'H'; 8974 } else { 8975 temp += this.template.charAt(i); 8976 } 8977 } 8978 this.template = temp; 8979 break; 8980 case "12": 8981 for (i = 0; i < this.template.length; i++) { 8982 if (this.template.charAt(i) == "'") { 8983 temp += this.template.charAt(i++); 8984 while (i < this.template.length && this.template.charAt(i) !== "'") { 8985 temp += this.template.charAt(i++); 8986 } 8987 if (i < this.template.length) { 8988 temp += this.template.charAt(i); 8989 } 8990 } else if (this.template.charAt(i) == 'k') { 8991 temp += 'K'; 8992 } else if (this.template.charAt(i) == 'H') { 8993 temp += 'h'; 8994 } else { 8995 temp += this.template.charAt(i); 8996 } 8997 } 8998 this.template = temp; 8999 break; 9000 } 9001 } 9002 9003 // tokenize it now for easy formatting 9004 this.templateArr = this._tokenize(this.template); 9005 9006 var digits; 9007 // set up the mapping to native or alternate digits if necessary 9008 if (typeof(this.useNative) === "boolean") { 9009 if (this.useNative) { 9010 digits = this.locinfo.getNativeDigits(); 9011 if (digits) { 9012 this.digits = digits; 9013 } 9014 } 9015 } else if (this.locinfo.getDigitsStyle() === "native") { 9016 digits = this.locinfo.getNativeDigits(); 9017 if (digits) { 9018 this.useNative = true; 9019 this.digits = digits; 9020 } 9021 } 9022 }, 9023 9024 /** 9025 * Convert the template into an array of date components separated by formatting chars. 9026 * @protected 9027 * @param {string} template Format template to tokenize into components 9028 * @return {Array.<string>} a tokenized array of date format components 9029 */ 9030 _tokenize: function (template) { 9031 var i = 0, start, ch, letter, arr = []; 9032 9033 // console.log("_tokenize: tokenizing template " + template); 9034 if (template) { 9035 while (i < template.length) { 9036 ch = template.charAt(i); 9037 start = i; 9038 if (ch === "'") { 9039 // console.log("found quoted string"); 9040 i++; 9041 // escaped string - push as-is, then dequote later 9042 while (i < template.length && template.charAt(i) !== "'") { 9043 i++; 9044 } 9045 if (i < template.length) { 9046 i++; // grab the other quote too 9047 } 9048 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 9049 letter = template.charAt(i); 9050 // console.log("found letters " + letter); 9051 while (i < template.length && ch === letter) { 9052 ch = template.charAt(++i); 9053 } 9054 } else { 9055 // console.log("found other"); 9056 while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { 9057 ch = template.charAt(++i); 9058 } 9059 } 9060 arr.push(template.substring(start,i)); 9061 // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); 9062 } 9063 } 9064 return arr; 9065 }, 9066 9067 /** 9068 * @protected 9069 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 9070 * @param {string} components Format components to search 9071 * @param {string} length Length of the requested format 9072 * @return {string|undefined} the requested format 9073 */ 9074 _getFormatInternal: function getFormatInternal(obj, components, length) { 9075 if (typeof(components) !== 'undefined' && obj && obj[components]) { 9076 return this._getLengthFormat(obj[components], length); 9077 } 9078 return undefined; 9079 }, 9080 9081 // stand-alone of m (month) is t 9082 // stand-alone of d (day) is a 9083 // stand-alone of w (weekday) is l 9084 // stand-alone of y (year) is r 9085 _standAlones: { 9086 "m": "t", 9087 "d": "a", 9088 "w": "l", 9089 "y": "r" 9090 }, 9091 9092 /** 9093 * @protected 9094 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 9095 * @param {string} components Format components to search 9096 * @param {string} length Length of the requested format 9097 * @return {string|undefined} the requested format 9098 */ 9099 _getFormat: function getFormat(obj, components, length) { 9100 // handle some special cases for stand-alone formats 9101 if (components && this._standAlones[components]) { 9102 var tmp = this._getFormatInternal(obj, this._standAlones[components], length); 9103 if (tmp) { 9104 return tmp; 9105 } 9106 } 9107 9108 // if no stand-alone format is available, fall back to the regular format 9109 return this._getFormatInternal(obj, components, length); 9110 }, 9111 9112 /** 9113 * @protected 9114 * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search 9115 * @param {string} length Length of the requested format 9116 * @return {(string|undefined)} the requested format 9117 */ 9118 _getLengthFormat: function getLengthFormat(obj, length) { 9119 if (typeof(obj) === 'string') { 9120 return obj; 9121 } else if (obj[length]) { 9122 return obj[length]; 9123 } 9124 return undefined; 9125 }, 9126 9127 /** 9128 * Return the locale used with this formatter instance. 9129 * @return {Locale} the Locale instance for this formatter 9130 */ 9131 getLocale: function() { 9132 return this.locale; 9133 }, 9134 9135 /** 9136 * Return the template string that is used to format date/times for this 9137 * formatter instance. This will work, even when the template property is not explicitly 9138 * given in the options to the constructor. Without the template option, the constructor 9139 * will build the appropriate template according to the options and use that template 9140 * in the format method. 9141 * 9142 * @return {string} the format template for this formatter 9143 */ 9144 getTemplate: function() { 9145 return this.template; 9146 }, 9147 9148 /** 9149 * Return the type of this formatter. The type is a string that has one of the following 9150 * values: "time", "date", "datetime". 9151 * @return {string} the type of the formatter 9152 */ 9153 getType: function() { 9154 return this.type; 9155 }, 9156 9157 /** 9158 * Return the name of the calendar used to format date/times for this 9159 * formatter instance. 9160 * @return {string} the name of the calendar used by this formatter 9161 */ 9162 getCalendar: function () { 9163 return this.cal.getType(); 9164 }, 9165 9166 /** 9167 * Return the length used to format date/times in this formatter. This is either the 9168 * value of the length option to the constructor, or the default value. 9169 * 9170 * @return {string} the length of formats this formatter returns 9171 */ 9172 getLength: function () { 9173 return DateFmt.lenmap[this.length] || ""; 9174 }, 9175 9176 /** 9177 * Return the date components that this formatter formats. This is either the 9178 * value of the date option to the constructor, or the default value. If this 9179 * formatter is a time-only formatter, this method will return the empty 9180 * string. The date component letters may be specified in any order in the 9181 * constructor, but this method will reorder the given components to a standard 9182 * order. 9183 * 9184 * @return {string} the date components that this formatter formats 9185 */ 9186 getDateComponents: function () { 9187 return this.dateComponents || ""; 9188 }, 9189 9190 /** 9191 * Return the time components that this formatter formats. This is either the 9192 * value of the time option to the constructor, or the default value. If this 9193 * formatter is a date-only formatter, this method will return the empty 9194 * string. The time component letters may be specified in any order in the 9195 * constructor, but this method will reorder the given components to a standard 9196 * order. 9197 * 9198 * @return {string} the time components that this formatter formats 9199 */ 9200 getTimeComponents: function () { 9201 return this.timeComponents || ""; 9202 }, 9203 9204 /** 9205 * Return the time zone used to format date/times for this formatter 9206 * instance. 9207 * @return a string naming the time zone 9208 */ 9209 getTimeZone: function () { 9210 // Lazy load the time zone. If it wasn't explicitly set up before, set 9211 // it up now, but use the 9212 // default TZ for the locale. This way, if the caller never uses the 9213 // time zone in their format, we never have to load up a TimeZone 9214 // instance into this formatter. 9215 if (!this.tz) { 9216 this.tz = new TimeZone({id: ilib.getTimeZone()}); 9217 } 9218 return this.tz; 9219 }, 9220 /** 9221 * Return the clock option set in the constructor. If the clock option was 9222 * not given, the default from the locale is returned instead. 9223 * @return {string} "12" or "24" depending on whether this formatter uses 9224 * the 12-hour or 24-hour clock 9225 */ 9226 getClock: function () { 9227 return this.clock || this.locinfo.getClock(); 9228 }, 9229 /** 9230 * Return the meridiems range in current locale. 9231 * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems 9232 */ 9233 getMeridiemsRange: function () { 9234 var result; 9235 var _getSysString = function (key) { 9236 return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); 9237 }; 9238 9239 switch (this.meridiems) { 9240 case "chinese": 9241 result = [ 9242 { 9243 name: _getSysString.call(this, "azh0"), 9244 start: "00:00", 9245 end: "05:59" 9246 }, 9247 { 9248 name: _getSysString.call(this, "azh1"), 9249 start: "06:00", 9250 end: "08:59" 9251 }, 9252 { 9253 name: _getSysString.call(this, "azh2"), 9254 start: "09:00", 9255 end: "11:59" 9256 }, 9257 { 9258 name: _getSysString.call(this, "azh3"), 9259 start: "12:00", 9260 end: "12:59" 9261 }, 9262 { 9263 name: _getSysString.call(this, "azh4"), 9264 start: "13:00", 9265 end: "17:59" 9266 }, 9267 { 9268 name: _getSysString.call(this, "azh5"), 9269 start: "18:00", 9270 end: "20:59" 9271 }, 9272 { 9273 name: _getSysString.call(this, "azh6"), 9274 start: "21:00", 9275 end: "23:59" 9276 } 9277 ]; 9278 break; 9279 case "ethiopic": 9280 result = [ 9281 { 9282 name: _getSysString.call(this, "a0-ethiopic"), 9283 start: "00:00", 9284 end: "05:59" 9285 }, 9286 { 9287 name: _getSysString.call(this, "a1-ethiopic"), 9288 start: "06:00", 9289 end: "06:00" 9290 }, 9291 { 9292 name: _getSysString.call(this, "a2-ethiopic"), 9293 start: "06:01", 9294 end: "11:59" 9295 }, 9296 { 9297 name: _getSysString.call(this, "a3-ethiopic"), 9298 start: "12:00", 9299 end: "17:59" 9300 }, 9301 { 9302 name: _getSysString.call(this, "a4-ethiopic"), 9303 start: "18:00", 9304 end: "23:59" 9305 } 9306 ]; 9307 break; 9308 default: 9309 result = [ 9310 { 9311 name: _getSysString.call(this, "a0"), 9312 start: "00:00", 9313 end: "11:59" 9314 }, 9315 { 9316 name: _getSysString.call(this, "a1"), 9317 start: "12:00", 9318 end: "23:59" 9319 } 9320 ]; 9321 break; 9322 } 9323 9324 return result; 9325 }, 9326 9327 /** 9328 * @private 9329 */ 9330 _getTemplate: function (prefix, calendar) { 9331 if (calendar !== "gregorian") { 9332 return prefix + "-" + calendar; 9333 } 9334 return prefix; 9335 }, 9336 9337 /** 9338 * Returns an array of the months of the year, formatted to the optional length specified. 9339 * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) 9340 * <p> 9341 * The options parameter may contain any of the following properties: 9342 * 9343 * <ul> 9344 * <li><i>length</i> - length of the names of the months being sought. This may be one of 9345 * "short", "medium", "long", or "full" 9346 * <li><i>date</i> - retrieve the names of the months in the date of the given date 9347 * <li><i>year</i> - retrieve the names of the months in the given year. In some calendars, 9348 * the months have different names depending if that year is a leap year or not. 9349 * </ul> 9350 * 9351 * @param {Object=} options an object-literal that contains any of the above properties 9352 * @return {Array} an array of the names of all of the months of the year in the current calendar 9353 */ 9354 getMonthsOfYear: function(options) { 9355 var length = (options && options.length) || this.getLength(), 9356 template = DateFmt.monthNameLenMap[length], 9357 months = [undefined], 9358 date, 9359 monthCount; 9360 9361 if (options) { 9362 if (options.date) { 9363 date = DateFactory._dateToIlib(options.date); 9364 } 9365 9366 if (options.year) { 9367 date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); 9368 } 9369 } 9370 9371 if (!date) { 9372 date = DateFactory({ 9373 calendar: this.cal.getType() 9374 }); 9375 } 9376 9377 monthCount = this.cal.getNumMonths(date.getYears()); 9378 for (var i = 1; i <= monthCount; i++) { 9379 months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 9380 } 9381 return months; 9382 }, 9383 9384 /** 9385 * Returns an array of the days of the week, formatted to the optional length specified. 9386 * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) 9387 * <p> 9388 * The options parameter may contain any of the following properties: 9389 * 9390 * <ul> 9391 * <li><i>length</i> - length of the names of the months being sought. This may be one of 9392 * "short", "medium", "long", or "full" 9393 * </ul> 9394 * @param {Object=} options an object-literal that contains one key 9395 * "length" with the standard length strings 9396 * @return {Array} an array of all of the names of the days of the week 9397 */ 9398 getDaysOfWeek: function(options) { 9399 var length = (options && options.length) || this.getLength(), 9400 template = DateFmt.weekDayLenMap[length], 9401 days = []; 9402 for (var i = 0; i < 7; i++) { 9403 days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 9404 } 9405 return days; 9406 }, 9407 9408 9409 /** 9410 * Convert this formatter to a string representation by returning the 9411 * format template. This method delegates to getTemplate. 9412 * 9413 * @return {string} the format template 9414 */ 9415 toString: function() { 9416 return this.getTemplate(); 9417 }, 9418 9419 /** 9420 * @private 9421 * Format a date according to a sequence of components. 9422 * @param {IDate} date a date/time object to format 9423 * @param {Array.<string>} templateArr an array of components to format 9424 * @return {string} the formatted date 9425 */ 9426 _formatTemplate: function (date, templateArr) { 9427 var i, key, temp, tz, str = ""; 9428 for (i = 0; i < templateArr.length; i++) { 9429 switch (templateArr[i]) { 9430 case 'd': 9431 str += (date.day || 1); 9432 break; 9433 case 'dd': 9434 str += JSUtils.pad(date.day || "1", 2); 9435 break; 9436 case 'yy': 9437 temp = "" + ((date.year || 0) % 100); 9438 str += JSUtils.pad(temp, 2); 9439 break; 9440 case 'yyyy': 9441 str += JSUtils.pad(date.year || "0", 4); 9442 break; 9443 case 'M': 9444 str += (date.month || 1); 9445 break; 9446 case 'MM': 9447 str += JSUtils.pad(date.month || "1", 2); 9448 break; 9449 case 'h': 9450 temp = (date.hour || 0) % 12; 9451 if (temp == 0) { 9452 temp = "12"; 9453 } 9454 str += temp; 9455 break; 9456 case 'hh': 9457 temp = (date.hour || 0) % 12; 9458 if (temp == 0) { 9459 temp = "12"; 9460 } 9461 str += JSUtils.pad(temp, 2); 9462 break; 9463 /* 9464 case 'j': 9465 temp = (date.hour || 0) % 12 + 1; 9466 str += temp; 9467 break; 9468 case 'jj': 9469 temp = (date.hour || 0) % 12 + 1; 9470 str += JSUtils.pad(temp, 2); 9471 break; 9472 */ 9473 case 'K': 9474 temp = (date.hour || 0) % 12; 9475 str += temp; 9476 break; 9477 case 'KK': 9478 temp = (date.hour || 0) % 12; 9479 str += JSUtils.pad(temp, 2); 9480 break; 9481 9482 case 'H': 9483 str += (date.hour || "0"); 9484 break; 9485 case 'HH': 9486 str += JSUtils.pad(date.hour || "0", 2); 9487 break; 9488 case 'k': 9489 str += (date.hour == 0 ? "24" : date.hour); 9490 break; 9491 case 'kk': 9492 temp = (date.hour == 0 ? "24" : date.hour); 9493 str += JSUtils.pad(temp, 2); 9494 break; 9495 9496 case 'm': 9497 str += (date.minute || "0"); 9498 break; 9499 case 'mm': 9500 str += JSUtils.pad(date.minute || "0", 2); 9501 break; 9502 case 's': 9503 str += (date.minute || "0"); 9504 break; 9505 case 'ss': 9506 str += JSUtils.pad(date.second || "0", 2); 9507 break; 9508 case 'S': 9509 str += (date.millisecond || "0"); 9510 break; 9511 case 'SSS': 9512 str += JSUtils.pad(date.millisecond || "0", 3); 9513 break; 9514 9515 case 'N': 9516 case 'NN': 9517 case 'MMM': 9518 case 'MMMM': 9519 case 'L': 9520 case 'LL': 9521 case 'LLL': 9522 case 'LLLL': 9523 key = templateArr[i] + (date.month || 1); 9524 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9525 break; 9526 9527 case 'E': 9528 case 'EE': 9529 case 'EEE': 9530 case 'EEEE': 9531 case 'c': 9532 case 'cc': 9533 case 'ccc': 9534 case 'cccc': 9535 key = templateArr[i] + date.getDayOfWeek(); 9536 //console.log("finding " + key + " in the resources"); 9537 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9538 break; 9539 9540 case 'a': 9541 switch (this.meridiems) { 9542 case "chinese": 9543 if (date.hour < 6) { 9544 key = "azh0"; // before dawn 9545 } else if (date.hour < 9) { 9546 key = "azh1"; // morning 9547 } else if (date.hour < 12) { 9548 key = "azh2"; // late morning/day before noon 9549 } else if (date.hour < 13) { 9550 key = "azh3"; // noon hour/midday 9551 } else if (date.hour < 18) { 9552 key = "azh4"; // afternoon 9553 } else if (date.hour < 21) { 9554 key = "azh5"; // evening time/dusk 9555 } else { 9556 key = "azh6"; // night time 9557 } 9558 break; 9559 case "ethiopic": 9560 if (date.hour < 6) { 9561 key = "a0-ethiopic"; // morning 9562 } else if (date.hour === 6 && date.minute === 0) { 9563 key = "a1-ethiopic"; // noon 9564 } else if (date.hour >= 6 && date.hour < 12) { 9565 key = "a2-ethiopic"; // afternoon 9566 } else if (date.hour >= 12 && date.hour < 18) { 9567 key = "a3-ethiopic"; // evening 9568 } else if (date.hour >= 18) { 9569 key = "a4-ethiopic"; // night 9570 } 9571 break; 9572 default: 9573 key = date.hour < 12 ? "a0" : "a1"; 9574 break; 9575 } 9576 //console.log("finding " + key + " in the resources"); 9577 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9578 break; 9579 9580 case 'w': 9581 str += date.getWeekOfYear(); 9582 break; 9583 case 'ww': 9584 str += JSUtils.pad(date.getWeekOfYear(), 2); 9585 break; 9586 9587 case 'D': 9588 str += date.getDayOfYear(); 9589 break; 9590 case 'DD': 9591 str += JSUtils.pad(date.getDayOfYear(), 2); 9592 break; 9593 case 'DDD': 9594 str += JSUtils.pad(date.getDayOfYear(), 3); 9595 break; 9596 case 'W': 9597 str += date.getWeekOfMonth(this.locale); 9598 break; 9599 9600 case 'G': 9601 key = "G" + date.getEra(); 9602 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9603 break; 9604 9605 case 'O': 9606 temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); 9607 str += temp.formatChoice(date.day, {num: date.day}); 9608 break; 9609 9610 case 'z': // general time zone 9611 tz = this.getTimeZone(); // lazy-load the tz 9612 str += tz.getDisplayName(date, "standard"); 9613 break; 9614 case 'Z': // RFC 822 time zone 9615 tz = this.getTimeZone(); // lazy-load the tz 9616 str += tz.getDisplayName(date, "rfc822"); 9617 break; 9618 9619 default: 9620 str += templateArr[i].replace(/'/g, ""); 9621 break; 9622 } 9623 } 9624 9625 if (this.digits) { 9626 str = JSUtils.mapString(str, this.digits); 9627 } 9628 return str; 9629 }, 9630 9631 /** 9632 * Format a particular date instance according to the settings of this 9633 * formatter object. The type of the date instance being formatted must 9634 * correspond exactly to the calendar type with which this formatter was 9635 * constructed. If the types are not compatible, this formatter will 9636 * produce bogus results. 9637 * 9638 * @param {IDate|Number|String|Date|JulianDay|null|undefined} dateLike a date-like object to format 9639 * @return {string} the formatted version of the given date instance 9640 */ 9641 format: function (dateLike) { 9642 var thisZoneName = this.tz && this.tz.getId() || "local"; 9643 9644 var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); 9645 9646 if (!date.getCalendar || !(date instanceof IDate)) { 9647 throw "Wrong date type passed to DateFmt.format()"; 9648 } 9649 9650 var dateZoneName = date.timezone || "local"; 9651 9652 // convert to the time zone of this formatter before formatting 9653 if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { 9654 // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); 9655 // this will recalculate the date components based on the new time zone 9656 // and/or convert a date in another calendar to the current calendar before formatting it 9657 var newDate = DateFactory({ 9658 type: this.calName, 9659 timezone: thisZoneName, 9660 julianday: date.getJulianDay() 9661 }); 9662 9663 date = newDate; 9664 } 9665 return this._formatTemplate(date, this.templateArr); 9666 }, 9667 9668 /** 9669 * Return a string that describes a date relative to the given 9670 * reference date. The string returned is text that for the locale that 9671 * was specified when the formatter instance was constructed.<p> 9672 * 9673 * The date can be in the future relative to the reference date or in 9674 * the past, and the formatter will generate the appropriate string.<p> 9675 * 9676 * The text used to describe the relative reference depends on the length 9677 * of time between the date and the reference. If the time was in the 9678 * past, it will use the "ago" phrase, and in the future, it will use 9679 * the "in" phrase. Examples:<p> 9680 * 9681 * <ul> 9682 * <li>within a minute: either "X seconds ago" or "in X seconds" 9683 * <li>within an hour: either "X minutes ago" or "in X minutes" 9684 * <li>within a day: either "X hours ago" or "in X hours" 9685 * <li>within 2 weeks: either "X days ago" or "in X days" 9686 * <li>within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" 9687 * <li>within two years: either "X months ago" or "in X months" 9688 * <li>longer than 2 years: "X years ago" or "in X years" 9689 * </ul> 9690 * 9691 * @param {IDate|Number|String|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to 9692 * @param {IDate|Number|String|Date|JulianDay|null|undefined} date a date being formatted 9693 * @throws "Wrong calendar type" when the start or end dates are not the same 9694 * calendar type as the formatter itself 9695 * @return {string} the formatted relative date 9696 */ 9697 formatRelative: function(reference, date) { 9698 reference = DateFactory._dateToIlib(reference); 9699 date = DateFactory._dateToIlib(date); 9700 9701 var referenceRd, dateRd, fmt, time, diff, num; 9702 9703 if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || 9704 typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { 9705 throw "Wrong calendar type"; 9706 } 9707 9708 referenceRd = reference.getRataDie(); 9709 dateRd = date.getRataDie(); 9710 9711 if (dateRd < referenceRd) { 9712 diff = referenceRd - dateRd; 9713 fmt = this.sysres.getString("{duration} ago"); 9714 } else { 9715 diff = dateRd - referenceRd; 9716 fmt = this.sysres.getString("in {duration}"); 9717 } 9718 9719 if (diff < 0.000694444) { 9720 num = Math.round(diff * 86400); 9721 switch (this.length) { 9722 case 's': 9723 time = this.sysres.getString("#{num}s"); 9724 break; 9725 case 'm': 9726 time = this.sysres.getString("1#1 se|#{num} sec"); 9727 break; 9728 case 'l': 9729 time = this.sysres.getString("1#1 sec|#{num} sec"); 9730 break; 9731 default: 9732 case 'f': 9733 time = this.sysres.getString("1#1 second|#{num} seconds"); 9734 break; 9735 } 9736 } else if (diff < 0.041666667) { 9737 num = Math.round(diff * 1440); 9738 switch (this.length) { 9739 case 's': 9740 time = this.sysres.getString("#{num}m", "durationShortMinutes"); 9741 break; 9742 case 'm': 9743 time = this.sysres.getString("1#1 mi|#{num} min"); 9744 break; 9745 case 'l': 9746 time = this.sysres.getString("1#1 min|#{num} min"); 9747 break; 9748 default: 9749 case 'f': 9750 time = this.sysres.getString("1#1 minute|#{num} minutes"); 9751 break; 9752 } 9753 } else if (diff < 1) { 9754 num = Math.round(diff * 24); 9755 switch (this.length) { 9756 case 's': 9757 time = this.sysres.getString("#{num}h"); 9758 break; 9759 case 'm': 9760 time = this.sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"); 9761 break; 9762 case 'l': 9763 time = this.sysres.getString("1#1 hr|#{num} hrs"); 9764 break; 9765 default: 9766 case 'f': 9767 time = this.sysres.getString("1#1 hour|#{num} hours"); 9768 break; 9769 } 9770 } else if (diff < 14) { 9771 num = Math.round(diff); 9772 switch (this.length) { 9773 case 's': 9774 time = this.sysres.getString("#{num}d"); 9775 break; 9776 case 'm': 9777 time = this.sysres.getString("1#1 dy|#{num} dys"); 9778 break; 9779 case 'l': 9780 time = this.sysres.getString("1#1 day|#{num} days", "durationLongDays"); 9781 break; 9782 default: 9783 case 'f': 9784 time = this.sysres.getString("1#1 day|#{num} days"); 9785 break; 9786 } 9787 } else if (diff < 84) { 9788 num = Math.round(diff/7); 9789 switch (this.length) { 9790 case 's': 9791 time = this.sysres.getString("#{num}w"); 9792 break; 9793 case 'm': 9794 time = this.sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"); 9795 break; 9796 case 'l': 9797 time = this.sysres.getString("1#1 wk|#{num} wks"); 9798 break; 9799 default: 9800 case 'f': 9801 time = this.sysres.getString("1#1 week|#{num} weeks"); 9802 break; 9803 } 9804 } else if (diff < 730) { 9805 num = Math.round(diff/30.4); 9806 switch (this.length) { 9807 case 's': 9808 time = this.sysres.getString("#{num}m", "durationShortMonths"); 9809 break; 9810 case 'm': 9811 time = this.sysres.getString("1#1 mo|#{num} mos"); 9812 break; 9813 case 'l': 9814 time = this.sysres.getString("1#1 mon|#{num} mons"); 9815 break; 9816 default: 9817 case 'f': 9818 time = this.sysres.getString("1#1 month|#{num} months"); 9819 break; 9820 } 9821 } else { 9822 num = Math.round(diff/365); 9823 switch (this.length) { 9824 case 's': 9825 time = this.sysres.getString("#{num}y"); 9826 break; 9827 case 'm': 9828 time = this.sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"); 9829 break; 9830 case 'l': 9831 time = this.sysres.getString("1#1 yr|#{num} yrs"); 9832 break; 9833 default: 9834 case 'f': 9835 time = this.sysres.getString("1#1 year|#{num} years"); 9836 break; 9837 } 9838 } 9839 return fmt.format({duration: time.formatChoice(num, {num: num})}); 9840 } 9841 }; 9842 9843 9844 9845 /*< DateRngFmt.js */ 9846 /* 9847 * DateFmt.js - Date formatter definition 9848 * 9849 * Copyright © 2012-2015, JEDLSoft 9850 * 9851 * Licensed under the Apache License, Version 2.0 (the "License"); 9852 * you may not use this file except in compliance with the License. 9853 * You may obtain a copy of the License at 9854 * 9855 * http://www.apache.org/licenses/LICENSE-2.0 9856 * 9857 * Unless required by applicable law or agreed to in writing, software 9858 * distributed under the License is distributed on an "AS IS" BASIS, 9859 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9860 * 9861 * See the License for the specific language governing permissions and 9862 * limitations under the License. 9863 */ 9864 9865 /* 9866 !depends 9867 ilib.js 9868 Locale.js 9869 IDate.js 9870 IString.js 9871 CalendarFactory.js 9872 LocaleInfo.js 9873 TimeZone.js 9874 DateFmt.js 9875 GregorianCal.js 9876 JSUtils.js 9877 Utils.js 9878 */ 9879 9880 // !data dateformats sysres 9881 9882 9883 9884 9885 9886 /** 9887 * @class 9888 * Create a new date range formatter instance. The date range formatter is immutable once 9889 * it is created, but can format as many different date ranges as needed with the same 9890 * options. Create different date range formatter instances for different purposes 9891 * and then keep them cached for use later if you have more than one range to 9892 * format.<p> 9893 * 9894 * The options may contain any of the following properties: 9895 * 9896 * <ul> 9897 * <li><i>locale</i> - locale to use when formatting the date/times in the range. If the 9898 * locale is not specified, then the default locale of the app or web page will be used. 9899 * 9900 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 9901 * be a sting containing the name of the calendar. Currently, the supported 9902 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 9903 * calendar is not specified, then the default calendar for the locale is used. When the 9904 * calendar type is specified, then the format method must be called with an instance of 9905 * the appropriate date type. (eg. Gregorian calendar means that the format method must 9906 * be called with a GregDate instance.) 9907 * 9908 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 9909 * instance or a time zone specifier string in RFC 822 format. If not specified, the 9910 * default time zone for the locale is used. 9911 * 9912 * <li><i>length</i> - Specify the length of the format to use as a string. The length 9913 * is the approximate size of the formatted string. 9914 * 9915 * <ul> 9916 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 9917 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 9918 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 9919 * components may still be abbreviated. (eg. "Tue" instead of "Tuesday") 9920 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 9921 * components are spelled out completely. 9922 * </ul> 9923 * 9924 * eg. The "short" format for an en_US range may be "MM/yy - MM/yy", whereas the long format might be 9925 * "MMM, yyyy - MMM, yyyy". In the long format, the month name is textual instead of numeric 9926 * and is longer, the year is 4 digits instead of 2, and the format contains slightly more 9927 * spaces and formatting characters.<p> 9928 * 9929 * Note that the length parameter does not specify which components are to be formatted. The 9930 * components that are formatted depend on the length of time in the range. 9931 * 9932 * <li><i>clock</i> - specify that formatted times should use a 12 or 24 hour clock if the 9933 * format happens to include times. Valid values are "12" and "24".<p> 9934 * 9935 * In some locales, both clocks are used. For example, in en_US, the general populace uses 9936 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 9937 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 9938 * construct a formatter that overrides the default for the locale.<p> 9939 * 9940 * If this property is not specified, the default is to use the most widely used convention 9941 * for the locale. 9942 * <li>onLoad - a callback function to call when the date range format object is fully 9943 * loaded. When the onLoad option is given, the DateRngFmt object will attempt to 9944 * load any missing locale data using the ilib loader callback. 9945 * When the constructor is done (even if the data is already preassembled), the 9946 * onLoad function is called with the current instance as a parameter, so this 9947 * callback can be used with preassembled or dynamic loading or a mix of the two. 9948 * 9949 * <li>sync - tell whether to load any missing locale data synchronously or 9950 * asynchronously. If this option is given as "false", then the "onLoad" 9951 * callback must be given, as the instance returned from this constructor will 9952 * not be usable for a while. 9953 * 9954 * <li><i>loadParams</i> - an object containing parameters to pass to the 9955 * loader callback function when locale data is missing. The parameters are not 9956 * interpretted or modified in any way. They are simply passed along. The object 9957 * may contain any property/value pairs as long as the calling code is in 9958 * agreement with the loader callback function as to what those parameters mean. 9959 * </ul> 9960 * <p> 9961 * 9962 * 9963 * @constructor 9964 * @param {Object} options options governing the way this date range formatter instance works 9965 */ 9966 var DateRngFmt = function(options) { 9967 var sync = true; 9968 var loadParams = undefined; 9969 this.locale = new Locale(); 9970 this.length = "s"; 9971 9972 if (options) { 9973 if (options.locale) { 9974 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 9975 } 9976 9977 if (options.calendar) { 9978 this.calName = options.calendar; 9979 } 9980 9981 if (options.length) { 9982 if (options.length === 'short' || 9983 options.length === 'medium' || 9984 options.length === 'long' || 9985 options.length === 'full') { 9986 // only use the first char to save space in the json files 9987 this.length = options.length.charAt(0); 9988 } 9989 } 9990 if (typeof(options.sync) !== 'undefined') { 9991 sync = (options.sync == true); 9992 } 9993 9994 loadParams = options.loadParams; 9995 } 9996 9997 var opts = {}; 9998 JSUtils.shallowCopy(options, opts); 9999 opts.sync = sync; 10000 opts.loadParams = loadParams; 10001 10002 /** 10003 * @private 10004 */ 10005 opts.onLoad = ilib.bind(this, function (fmt) { 10006 this.dateFmt = fmt; 10007 if (fmt) { 10008 this.locinfo = this.dateFmt.locinfo; 10009 10010 // get the default calendar name from the locale, and if the locale doesn't define 10011 // one, use the hard-coded gregorian as the last resort 10012 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 10013 this.cal = CalendarFactory({ 10014 type: this.calName 10015 }); 10016 if (!this.cal) { 10017 this.cal = new GregorianCal(); 10018 } 10019 10020 this.timeTemplate = this.dateFmt._getFormat(this.dateFmt.formats.time[this.dateFmt.clock], this.dateFmt.timeComponents, this.length) || "hh:mm"; 10021 this.timeTemplateArr = this.dateFmt._tokenize(this.timeTemplate); 10022 10023 if (options && typeof(options.onLoad) === 'function') { 10024 options.onLoad(this); 10025 } 10026 } 10027 }); 10028 10029 // delegate a bunch of the formatting to this formatter 10030 new DateFmt(opts); 10031 }; 10032 10033 DateRngFmt.prototype = { 10034 /** 10035 * Return the locale used with this formatter instance. 10036 * @return {Locale} the Locale instance for this formatter 10037 */ 10038 getLocale: function() { 10039 return this.locale; 10040 }, 10041 10042 /** 10043 * Return the name of the calendar used to format date/times for this 10044 * formatter instance. 10045 * @return {string} the name of the calendar used by this formatter 10046 */ 10047 getCalendar: function () { 10048 return this.dateFmt.getCalendar(); 10049 }, 10050 10051 /** 10052 * Return the length used to format date/times in this formatter. This is either the 10053 * value of the length option to the constructor, or the default value. 10054 * 10055 * @return {string} the length of formats this formatter returns 10056 */ 10057 getLength: function () { 10058 return DateFmt.lenmap[this.length] || ""; 10059 }, 10060 10061 /** 10062 * Return the time zone used to format date/times for this formatter 10063 * instance. 10064 * @return {TimeZone} a string naming the time zone 10065 */ 10066 getTimeZone: function () { 10067 return this.dateFmt.getTimeZone(); 10068 }, 10069 10070 /** 10071 * Return the clock option set in the constructor. If the clock option was 10072 * not given, the default from the locale is returned instead. 10073 * @return {string} "12" or "24" depending on whether this formatter uses 10074 * the 12-hour or 24-hour clock 10075 */ 10076 getClock: function () { 10077 return this.dateFmt.getClock(); 10078 }, 10079 10080 /** 10081 * Format a date/time range according to the settings of the current 10082 * formatter. The range is specified as being from the "start" date until 10083 * the "end" date. <p> 10084 * 10085 * The template that the date/time range uses depends on the 10086 * length of time between the dates, on the premise that a long date range 10087 * which is too specific is not useful. For example, when giving 10088 * the dates of the 100 Years War, in most situations it would be more 10089 * appropriate to format the range as "1337 - 1453" than to format it as 10090 * "10:37am November 9, 1337 - 4:37pm July 17, 1453", as the latter format 10091 * is much too specific given the length of time that the range represents. 10092 * If a very specific, but long, date range really is needed, the caller 10093 * should format two specific dates separately and put them 10094 * together as you might with other normal strings.<p> 10095 * 10096 * The format used for a date range contains the following date components, 10097 * where the order of those components is rearranged and the component values 10098 * are translated according to each locale: 10099 * 10100 * <ul> 10101 * <li>within 3 days: the times of day, dates, months, and years 10102 * <li>within 730 days (2 years): the dates, months, and years 10103 * <li>within 3650 days (10 years): the months and years 10104 * <li>longer than 10 years: the years only 10105 * </ul> 10106 * 10107 * In general, if any of the date components share a value between the 10108 * start and end date, that component is only given once. For example, 10109 * if the range is from November 15, 2011 to November 26, 2011, the 10110 * start and end dates both share the same month and year. The 10111 * range would then be formatted as "November 15-26, 2011". <p> 10112 * 10113 * If you want to format a length of time instead of a particular range of 10114 * time (for example, the length of an event rather than the specific start time 10115 * and end time of that event), then use a duration formatter instance 10116 * (DurationFmt) instead. The formatRange method will make sure that each component 10117 * of the date/time is within the normal range for that component. For example, 10118 * the minutes will always be between 0 and 59, no matter what is specified in 10119 * the date to format, because that is the normal range for minutes. A duration 10120 * format will allow the number of minutes to exceed 59. For example, if you 10121 * were displaying the length of a movie that is 198 minutes long, the minutes 10122 * component of a duration could be 198.<p> 10123 * 10124 * @param {IDate} start the starting date/time of the range. This must be of 10125 * the same calendar type as the formatter itself. 10126 * @param {IDate} end the ending date/time of the range. This must be of the 10127 * same calendar type as the formatter itself. 10128 * @throws "Wrong calendar type" when the start or end dates are not the same 10129 * calendar type as the formatter itself 10130 * @return {string} a date range formatted for the locale 10131 */ 10132 format: function (start, end) { 10133 var startRd, endRd, fmt = "", yearTemplate, monthTemplate, dayTemplate, formats; 10134 10135 if (typeof(start) !== 'object' || !start.getCalendar || start.getCalendar() !== this.calName || 10136 typeof(end) !== 'object' || !end.getCalendar || end.getCalendar() !== this.calName) { 10137 throw "Wrong calendar type"; 10138 } 10139 10140 startRd = start.getRataDie(); 10141 endRd = end.getRataDie(); 10142 10143 // 10144 // legend: 10145 // c00 - difference is less than 3 days. Year, month, and date are same, but time is different 10146 // c01 - difference is less than 3 days. Year and month are same but date and time are different 10147 // c02 - difference is less than 3 days. Year is same but month, date, and time are different. (ie. it straddles a month boundary) 10148 // c03 - difference is less than 3 days. Year, month, date, and time are all different. (ie. it straddles a year boundary) 10149 // c10 - difference is less than 2 years. Year and month are the same, but date is different. 10150 // c11 - difference is less than 2 years. Year is the same, but month, date, and time are different. 10151 // c12 - difference is less than 2 years. All fields are different. (ie. straddles a year boundary) 10152 // c20 - difference is less than 10 years. All fields are different. 10153 // c30 - difference is more than 10 years. All fields are different. 10154 // 10155 10156 if (endRd - startRd < 3) { 10157 if (start.year === end.year) { 10158 if (start.month === end.month) { 10159 if (start.day === end.day) { 10160 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c00", this.length)); 10161 } else { 10162 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c01", this.length)); 10163 } 10164 } else { 10165 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c02", this.length)); 10166 } 10167 } else { 10168 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c03", this.length)); 10169 } 10170 } else if (endRd - startRd < 730) { 10171 if (start.year === end.year) { 10172 if (start.month === end.month) { 10173 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c10", this.length)); 10174 } else { 10175 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c11", this.length)); 10176 } 10177 } else { 10178 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c12", this.length)); 10179 } 10180 } else if (endRd - startRd < 3650) { 10181 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c20", this.length)); 10182 } else { 10183 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c30", this.length)); 10184 } 10185 10186 formats = this.dateFmt.formats.date; 10187 yearTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "y", this.length) || "yyyy"); 10188 monthTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "m", this.length) || "MM"); 10189 dayTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "d", this.length) || "dd"); 10190 10191 /* 10192 console.log("fmt is " + fmt.toString()); 10193 console.log("year template is " + yearTemplate); 10194 console.log("month template is " + monthTemplate); 10195 console.log("day template is " + dayTemplate); 10196 */ 10197 10198 return fmt.format({ 10199 sy: this.dateFmt._formatTemplate(start, yearTemplate), 10200 sm: this.dateFmt._formatTemplate(start, monthTemplate), 10201 sd: this.dateFmt._formatTemplate(start, dayTemplate), 10202 st: this.dateFmt._formatTemplate(start, this.timeTemplateArr), 10203 ey: this.dateFmt._formatTemplate(end, yearTemplate), 10204 em: this.dateFmt._formatTemplate(end, monthTemplate), 10205 ed: this.dateFmt._formatTemplate(end, dayTemplate), 10206 et: this.dateFmt._formatTemplate(end, this.timeTemplateArr) 10207 }); 10208 } 10209 }; 10210 10211 10212 /*< HebrewCal.js */ 10213 /* 10214 * hebrew.js - Represent a Hebrew calendar object. 10215 * 10216 * Copyright © 2012-2015, JEDLSoft 10217 * 10218 * Licensed under the Apache License, Version 2.0 (the "License"); 10219 * you may not use this file except in compliance with the License. 10220 * You may obtain a copy of the License at 10221 * 10222 * http://www.apache.org/licenses/LICENSE-2.0 10223 * 10224 * Unless required by applicable law or agreed to in writing, software 10225 * distributed under the License is distributed on an "AS IS" BASIS, 10226 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10227 * 10228 * See the License for the specific language governing permissions and 10229 * limitations under the License. 10230 */ 10231 10232 10233 /* !depends ilib.js Calendar.js MathUtils.js */ 10234 10235 10236 /** 10237 * @class 10238 * Construct a new Hebrew calendar object. This class encodes information about 10239 * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew 10240 * calendar where the dates are calculated by arithmetic rules. This differs from 10241 * the religious Hebrew calendar which is used to mark the beginning of particular 10242 * holidays. The religious calendar depends on the first sighting of the new 10243 * crescent moon to determine the first day of the new month. Because humans and 10244 * weather are both involved, the actual time of sighting varies, so it is not 10245 * really possible to precalculate the religious calendar. Certain groups, such 10246 * as the Hebrew Society of North America, decreed in in 2007 that they will use 10247 * a calendar based on calculations rather than observations to determine the 10248 * beginning of lunar months, and therefore the dates of holidays.<p> 10249 * 10250 * 10251 * @constructor 10252 * @extends Calendar 10253 */ 10254 var HebrewCal = function() { 10255 this.type = "hebrew"; 10256 }; 10257 10258 /** 10259 * Return the number of days elapsed in the Hebrew calendar before the 10260 * given year starts. 10261 * @private 10262 * @param {number} year the year for which the number of days is sought 10263 * @return {number} the number of days elapsed in the Hebrew calendar before the 10264 * given year starts 10265 */ 10266 HebrewCal.elapsedDays = function(year) { 10267 var months = Math.floor(((235*year) - 234)/19); 10268 var parts = 204 + 793 * MathUtils.mod(months, 1080); 10269 var hours = 11 + 12 * months + 793 * Math.floor(months/1080) + 10270 Math.floor(parts/1080); 10271 var days = 29 * months + Math.floor(hours/24); 10272 return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days; 10273 }; 10274 10275 /** 10276 * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew 10277 * calendar will be corrected for the given year. Corrections are caused because New 10278 * Year's is not allowed to start on certain days of the week. To deal with 10279 * it, the start of the new year is corrected for the next year by adding a 10280 * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current 10281 * year to make them 30 days long instead of 29. 10282 * 10283 * @private 10284 * @param {number} year the year for which the correction is sought 10285 * @param {number} elapsed number of days elapsed up to this year 10286 * @return {number} the number of days correction in the current year to make sure 10287 * Rosh HaShanah does not fall on undesirable days of the week 10288 */ 10289 HebrewCal.newYearsCorrection = function(year, elapsed) { 10290 var lastYear = HebrewCal.elapsedDays(year-1), 10291 thisYear = elapsed, 10292 nextYear = HebrewCal.elapsedDays(year+1); 10293 10294 return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0); 10295 }; 10296 10297 /** 10298 * Return the rata die date of the new year for the given hebrew year. 10299 * @private 10300 * @param {number} year the year for which the new year is needed 10301 * @return {number} the rata die date of the new year 10302 */ 10303 HebrewCal.newYear = function(year) { 10304 var elapsed = HebrewCal.elapsedDays(year); 10305 10306 return elapsed + HebrewCal.newYearsCorrection(year, elapsed); 10307 }; 10308 10309 /** 10310 * Return the number of days in the given year. Years contain a variable number of 10311 * days because the date of Rosh HaShanah (New Year's) changes so that it doesn't 10312 * fall on particular days of the week. Days are added to the months of Heshvan 10313 * and/or Kislev in the previous year in order to prevent the current year's New 10314 * Year from being on Sunday, Wednesday, or Friday. 10315 * 10316 * @param {number} year the year for which the length is sought 10317 * @return {number} number of days in the given year 10318 */ 10319 HebrewCal.daysInYear = function(year) { 10320 return HebrewCal.newYear(year+1) - HebrewCal.newYear(year); 10321 }; 10322 10323 /** 10324 * Return true if the given year contains a long month of Heshvan. That is, 10325 * it is 30 days instead of 29. 10326 * 10327 * @private 10328 * @param {number} year the year in which that month is questioned 10329 * @return {boolean} true if the given year contains a long month of Heshvan 10330 */ 10331 HebrewCal.longHeshvan = function(year) { 10332 return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5; 10333 }; 10334 10335 /** 10336 * Return true if the given year contains a long month of Kislev. That is, 10337 * it is 30 days instead of 29. 10338 * 10339 * @private 10340 * @param {number} year the year in which that month is questioned 10341 * @return {boolean} true if the given year contains a short month of Kislev 10342 */ 10343 HebrewCal.longKislev = function(year) { 10344 return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3; 10345 }; 10346 10347 /** 10348 * Return the date of the last day of the month for the given year. The date of 10349 * the last day of the month is variable because a number of months gain an extra 10350 * day in leap years, and it is variable which months gain a day for each leap 10351 * year and which do not. 10352 * 10353 * @param {number} month the month for which the number of days is sought 10354 * @param {number} year the year in which that month is 10355 * @return {number} the number of days in the given month and year 10356 */ 10357 HebrewCal.prototype.lastDayOfMonth = function(month, year) { 10358 switch (month) { 10359 case 2: 10360 case 4: 10361 case 6: 10362 case 10: 10363 return 29; 10364 case 13: 10365 return this.isLeapYear(year) ? 29 : 0; 10366 case 8: 10367 return HebrewCal.longHeshvan(year) ? 30 : 29; 10368 case 9: 10369 return HebrewCal.longKislev(year) ? 30 : 29; 10370 case 12: 10371 case 1: 10372 case 3: 10373 case 5: 10374 case 7: 10375 case 11: 10376 return 30; 10377 default: 10378 return 0; 10379 } 10380 }; 10381 10382 /** 10383 * Return the number of months in the given year. The number of months in a year varies 10384 * for luni-solar calendars because in some years, an extra month is needed to extend the 10385 * days in a year to an entire solar year. The month is represented as a 1-based number 10386 * where 1=first month, 2=second month, etc. 10387 * 10388 * @param {number} year a year for which the number of months is sought 10389 */ 10390 HebrewCal.prototype.getNumMonths = function(year) { 10391 return this.isLeapYear(year) ? 13 : 12; 10392 }; 10393 10394 /** 10395 * Return the number of days in a particular month in a particular year. This function 10396 * can return a different number for a month depending on the year because of leap years. 10397 * 10398 * @param {number} month the month for which the length is sought 10399 * @param {number} year the year within which that month can be found 10400 * @returns {number} the number of days within the given month in the given year, or 10401 * 0 for an invalid month in the year 10402 */ 10403 HebrewCal.prototype.getMonLength = function(month, year) { 10404 if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) { 10405 return 0; 10406 } 10407 return this.lastDayOfMonth(month, year); 10408 }; 10409 10410 /** 10411 * Return true if the given year is a leap year in the Hebrew calendar. 10412 * The year parameter may be given as a number, or as a HebrewDate object. 10413 * @param {number|Object} year the year for which the leap year information is being sought 10414 * @returns {boolean} true if the given year is a leap year 10415 */ 10416 HebrewCal.prototype.isLeapYear = function(year) { 10417 var y = (typeof(year) == 'number') ? year : year.year; 10418 return (MathUtils.mod(1 + 7 * y, 19) < 7); 10419 }; 10420 10421 /** 10422 * Return the type of this calendar. 10423 * 10424 * @returns {string} the name of the type of this calendar 10425 */ 10426 HebrewCal.prototype.getType = function() { 10427 return this.type; 10428 }; 10429 10430 /** 10431 * Return a date instance for this calendar type using the given 10432 * options. 10433 * @param {Object} options options controlling the construction of 10434 * the date instance 10435 * @returns {HebrewDate} a date appropriate for this calendar type 10436 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 10437 */ 10438 HebrewCal.prototype.newDateInstance = function (options) { 10439 return new HebrewDate(options); 10440 }; 10441 10442 /*register this calendar for the factory method */ 10443 Calendar._constructors["hebrew"] = HebrewCal; 10444 10445 10446 10447 /*< HebrewRataDie.js */ 10448 /* 10449 * HebrewRataDie.js - Represent an RD date in the Hebrew calendar 10450 * 10451 * Copyright © 2012-2015, JEDLSoft 10452 * 10453 * Licensed under the Apache License, Version 2.0 (the "License"); 10454 * you may not use this file except in compliance with the License. 10455 * You may obtain a copy of the License at 10456 * 10457 * http://www.apache.org/licenses/LICENSE-2.0 10458 * 10459 * Unless required by applicable law or agreed to in writing, software 10460 * distributed under the License is distributed on an "AS IS" BASIS, 10461 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10462 * 10463 * See the License for the specific language governing permissions and 10464 * limitations under the License. 10465 */ 10466 10467 /* !depends 10468 MathUtils.js 10469 HebrewCal.js 10470 RataDie.js 10471 */ 10472 10473 10474 /** 10475 * @class 10476 * Construct a new Hebrew RD date number object. The constructor parameters can 10477 * contain any of the following properties: 10478 * 10479 * <ul> 10480 * <li><i>unixtime<i> - sets the time of this instance according to the given 10481 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 10482 * 10483 * <li><i>julianday</i> - sets the time of this instance according to the given 10484 * Julian Day instance or the Julian Day given as a float 10485 * 10486 * <li><i>year</i> - any integer, including 0 10487 * 10488 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 10489 * 10490 * <li><i>day</i> - 1 to 31 10491 * 10492 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10493 * is always done with an unambiguous 24 hour representation 10494 * 10495 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10496 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10497 * 10498 * <li><i>minute</i> - 0 to 59 10499 * 10500 * <li><i>second</i> - 0 to 59 10501 * 10502 * <li><i>millisecond</i> - 0 to 999 10503 * 10504 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10505 * </ul> 10506 * 10507 * If the constructor is called with another Hebrew date instance instead of 10508 * a parameter block, the other instance acts as a parameter block and its 10509 * settings are copied into the current instance.<p> 10510 * 10511 * If the constructor is called with no arguments at all or if none of the 10512 * properties listed above are present, then the RD is calculate based on 10513 * the current date at the time of instantiation. <p> 10514 * 10515 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 10516 * specified in the params, it is assumed that they have the smallest possible 10517 * value in the range for the property (zero or one).<p> 10518 * 10519 * 10520 * @private 10521 * @constructor 10522 * @extends RataDie 10523 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew RD date 10524 */ 10525 var HebrewRataDie = function(params) { 10526 this.cal = params && params.cal || new HebrewCal(); 10527 this.rd = NaN; 10528 RataDie.call(this, params); 10529 }; 10530 10531 HebrewRataDie.prototype = new RataDie(); 10532 HebrewRataDie.prototype.parent = RataDie; 10533 HebrewRataDie.prototype.constructor = HebrewRataDie; 10534 10535 /** 10536 * The difference between a zero Julian day and the first day of the Hebrew 10537 * calendar: sunset on Monday, Tishri 1, 1 = September 7, 3760 BC Gregorian = JD 347997.25 10538 * @private 10539 * @type number 10540 */ 10541 HebrewRataDie.prototype.epoch = 347997.25; 10542 10543 /** 10544 * the cumulative lengths of each month for a non-leap year, without new years corrections 10545 * @private 10546 * @const 10547 * @type Array.<number> 10548 */ 10549 HebrewRataDie.cumMonthLengths = [ 10550 176, /* Nisan */ 10551 206, /* Iyyar */ 10552 235, /* Sivan */ 10553 265, /* Tammuz */ 10554 294, /* Av */ 10555 324, /* Elul */ 10556 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10557 30, /* Heshvan */ 10558 59, /* Kislev */ 10559 88, /* Teveth */ 10560 117, /* Shevat */ 10561 147 /* Adar I */ 10562 ]; 10563 10564 /** 10565 * the cumulative lengths of each month for a leap year, without new years corrections 10566 * @private 10567 * @const 10568 * @type Array.<number> 10569 */ 10570 HebrewRataDie.cumMonthLengthsLeap = [ 10571 206, /* Nisan */ 10572 236, /* Iyyar */ 10573 265, /* Sivan */ 10574 295, /* Tammuz */ 10575 324, /* Av */ 10576 354, /* Elul */ 10577 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10578 30, /* Heshvan */ 10579 59, /* Kislev */ 10580 88, /* Teveth */ 10581 117, /* Shevat */ 10582 147, /* Adar I */ 10583 177 /* Adar II */ 10584 ]; 10585 10586 /** 10587 * Calculate the Rata Die (fixed day) number of the given date from the 10588 * date components. 10589 * 10590 * @private 10591 * @param {Object} date the date components to calculate the RD from 10592 */ 10593 HebrewRataDie.prototype._setDateComponents = function(date) { 10594 var elapsed = HebrewCal.elapsedDays(date.year); 10595 var days = elapsed + 10596 HebrewCal.newYearsCorrection(date.year, elapsed) + 10597 date.day - 1; 10598 var sum = 0, table; 10599 10600 //console.log("getRataDie: converting " + JSON.stringify(date)); 10601 //console.log("getRataDie: days is " + days); 10602 //console.log("getRataDie: new years correction is " + HebrewCal.newYearsCorrection(date.year, elapsed)); 10603 10604 table = this.cal.isLeapYear(date.year) ? 10605 HebrewRataDie.cumMonthLengthsLeap : 10606 HebrewRataDie.cumMonthLengths; 10607 sum = table[date.month-1]; 10608 10609 // gets cumulative without correction, so now add in the correction 10610 if ((date.month < 7 || date.month > 8) && HebrewCal.longHeshvan(date.year)) { 10611 sum++; 10612 } 10613 if ((date.month < 7 || date.month > 9) && HebrewCal.longKislev(date.year)) { 10614 sum++; 10615 } 10616 // console.log("getRataDie: cum days is now " + sum); 10617 10618 days += sum; 10619 10620 // the date starts at sunset, which we take as 18:00, so the hours from 10621 // midnight to 18:00 are on the current Gregorian day, and the hours from 10622 // 18:00 to midnight are on the previous Gregorian day. So to calculate the 10623 // number of hours into the current day that this time represents, we have 10624 // to count from 18:00 to midnight first, and add in 6 hours if the time is 10625 // less than 18:00 10626 var minute, second, millisecond; 10627 10628 if (typeof(date.parts) !== 'undefined') { 10629 // The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10630 var parts = parseInt(date.parts, 10); 10631 var seconds = parseInt(parts, 10) * 3.333333333333; 10632 minute = Math.floor(seconds / 60); 10633 seconds -= minute * 60; 10634 second = Math.floor(seconds); 10635 millisecond = (seconds - second); 10636 } else { 10637 minute = parseInt(date.minute, 10) || 0; 10638 second = parseInt(date.second, 10) || 0; 10639 millisecond = parseInt(date.millisecond, 10) || 0; 10640 } 10641 10642 var time; 10643 if (date.hour >= 18) { 10644 time = ((date.hour - 18 || 0) * 3600000 + 10645 (minute || 0) * 60000 + 10646 (second || 0) * 1000 + 10647 (millisecond || 0)) / 10648 86400000; 10649 } else { 10650 time = 0.25 + // 6 hours from 18:00 to midnight on the previous gregorian day 10651 ((date.hour || 0) * 3600000 + 10652 (minute || 0) * 60000 + 10653 (second || 0) * 1000 + 10654 (millisecond || 0)) / 10655 86400000; 10656 } 10657 10658 //console.log("getRataDie: rd is " + (days + time)); 10659 this.rd = days + time; 10660 }; 10661 10662 /** 10663 * Return the rd number of the particular day of the week on or before the 10664 * given rd. eg. The Sunday on or before the given rd. 10665 * @private 10666 * @param {number} rd the rata die date of the reference date 10667 * @param {number} dayOfWeek the day of the week that is being sought relative 10668 * to the current date 10669 * @return {number} the rd of the day of the week 10670 */ 10671 HebrewRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 10672 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek + 1, 7); 10673 }; 10674 10675 10676 10677 /*< HebrewDate.js */ 10678 /* 10679 * HebrewDate.js - Represent a date in the Hebrew calendar 10680 * 10681 * Copyright © 2012-2015, JEDLSoft 10682 * 10683 * Licensed under the Apache License, Version 2.0 (the "License"); 10684 * you may not use this file except in compliance with the License. 10685 * You may obtain a copy of the License at 10686 * 10687 * http://www.apache.org/licenses/LICENSE-2.0 10688 * 10689 * Unless required by applicable law or agreed to in writing, software 10690 * distributed under the License is distributed on an "AS IS" BASIS, 10691 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10692 * 10693 * See the License for the specific language governing permissions and 10694 * limitations under the License. 10695 */ 10696 10697 /* !depends 10698 ilib.js 10699 Locale.js 10700 LocaleInfo.js 10701 TimeZone.js 10702 IDate.js 10703 MathUtils.js 10704 Calendar.js 10705 HebrewCal.js 10706 HebrewRataDie.js 10707 */ 10708 10709 10710 10711 10712 /** 10713 * @class 10714 * Construct a new civil Hebrew date object. The constructor can be called 10715 * with a params object that can contain the following properties:<p> 10716 * 10717 * <ul> 10718 * <li><i>julianday</i> - the Julian Day to set into this date 10719 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 10720 * <li><i>month</i> - 1 to 12, where 1 means Nisan, 2 means Iyyar, etc. 10721 * <li><i>day</i> - 1 to 30 10722 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10723 * is always done with an unambiguous 24 hour representation 10724 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10725 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10726 * <li><i>minute</i> - 0 to 59 10727 * <li><i>second</i> - 0 to 59 10728 * <li><i>millisecond</i> - 0 to 999 10729 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 10730 * of this julian date. The date/time is kept in the local time. The time zone 10731 * is used later if this date is formatted according to a different time zone and 10732 * the difference has to be calculated, or when the date format has a time zone 10733 * component in it. 10734 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 10735 * given, it can be inferred from this locale. For locales that span multiple 10736 * time zones, the one with the largest population is chosen as the one that 10737 * represents the locale. 10738 * 10739 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10740 * </ul> 10741 * 10742 * If called with another Hebrew date argument, the date components of the given 10743 * date are copied into the current one.<p> 10744 * 10745 * If the constructor is called with no arguments at all or if none of the 10746 * properties listed above 10747 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 10748 * components are 10749 * filled in with the current date at the time of instantiation. Note that if 10750 * you do not give the time zone when defaulting to the current time and the 10751 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 10752 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 10753 * Mean Time").<p> 10754 * 10755 * 10756 * @constructor 10757 * @extends IDate 10758 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew date 10759 */ 10760 var HebrewDate = function(params) { 10761 this.cal = new HebrewCal(); 10762 10763 if (params) { 10764 if (params.timezone) { 10765 this.timezone = params.timezone; 10766 } 10767 if (params.locale) { 10768 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 10769 if (!this.timezone) { 10770 var li = new LocaleInfo(this.locale); 10771 this.timezone = li.getTimeZone(); 10772 } 10773 } 10774 10775 if (params.year || params.month || params.day || params.hour || 10776 params.minute || params.second || params.millisecond || params.parts ) { 10777 /** 10778 * Year in the Hebrew calendar. 10779 * @type number 10780 */ 10781 this.year = parseInt(params.year, 10) || 0; 10782 10783 /** 10784 * The month number, ranging from 1 to 13. 10785 * @type number 10786 */ 10787 this.month = parseInt(params.month, 10) || 1; 10788 10789 /** 10790 * The day of the month. This ranges from 1 to 30. 10791 * @type number 10792 */ 10793 this.day = parseInt(params.day, 10) || 1; 10794 10795 /** 10796 * The hour of the day. This can be a number from 0 to 23, as times are 10797 * stored unambiguously in the 24-hour clock. 10798 * @type number 10799 */ 10800 this.hour = parseInt(params.hour, 10) || 0; 10801 10802 if (typeof(params.parts) !== 'undefined') { 10803 /** 10804 * The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10805 * @type number 10806 */ 10807 this.parts = parseInt(params.parts, 10); 10808 var seconds = parseInt(params.parts, 10) * 3.333333333333; 10809 this.minute = Math.floor(seconds / 60); 10810 seconds -= this.minute * 60; 10811 this.second = Math.floor(seconds); 10812 this.millisecond = (seconds - this.second); 10813 } else { 10814 /** 10815 * The minute of the hours. Ranges from 0 to 59. 10816 * @type number 10817 */ 10818 this.minute = parseInt(params.minute, 10) || 0; 10819 10820 /** 10821 * The second of the minute. Ranges from 0 to 59. 10822 * @type number 10823 */ 10824 this.second = parseInt(params.second, 10) || 0; 10825 10826 /** 10827 * The millisecond of the second. Ranges from 0 to 999. 10828 * @type number 10829 */ 10830 this.millisecond = parseInt(params.millisecond, 10) || 0; 10831 } 10832 10833 /** 10834 * The day of the year. Ranges from 1 to 383. 10835 * @type number 10836 */ 10837 this.dayOfYear = parseInt(params.dayOfYear, 10); 10838 10839 if (typeof(params.dst) === 'boolean') { 10840 this.dst = params.dst; 10841 } 10842 10843 this.rd = this.newRd(this); 10844 10845 // add the time zone offset to the rd to convert to UTC 10846 if (!this.tz) { 10847 this.tz = new TimeZone({id: this.timezone}); 10848 } 10849 // getOffsetMillis requires that this.year, this.rd, and this.dst 10850 // are set in order to figure out which time zone rules apply and 10851 // what the offset is at that point in the year 10852 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 10853 if (this.offset !== 0) { 10854 this.rd = this.newRd({ 10855 rd: this.rd.getRataDie() - this.offset 10856 }); 10857 } 10858 } 10859 } 10860 10861 if (!this.rd) { 10862 this.rd = this.newRd(params); 10863 this._calcDateComponents(); 10864 } 10865 }; 10866 10867 HebrewDate.prototype = new IDate({noinstance: true}); 10868 HebrewDate.prototype.parent = IDate; 10869 HebrewDate.prototype.constructor = HebrewDate; 10870 10871 /** 10872 * the cumulative lengths of each month for a non-leap year, without new years corrections, 10873 * that can be used in reverse to map days to months 10874 * @private 10875 * @const 10876 * @type Array.<number> 10877 */ 10878 HebrewDate.cumMonthLengthsReverse = [ 10879 // [days, monthnumber], 10880 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10881 [30, 8], /* Heshvan */ 10882 [59, 9], /* Kislev */ 10883 [88, 10], /* Teveth */ 10884 [117, 11], /* Shevat */ 10885 [147, 12], /* Adar I */ 10886 [176, 1], /* Nisan */ 10887 [206, 2], /* Iyyar */ 10888 [235, 3], /* Sivan */ 10889 [265, 4], /* Tammuz */ 10890 [294, 5], /* Av */ 10891 [324, 6], /* Elul */ 10892 [354, 7] /* end of year sentinel value */ 10893 ]; 10894 10895 /** 10896 * the cumulative lengths of each month for a leap year, without new years corrections 10897 * that can be used in reverse to map days to months 10898 * 10899 * @private 10900 * @const 10901 * @type Array.<number> 10902 */ 10903 HebrewDate.cumMonthLengthsLeapReverse = [ 10904 // [days, monthnumber], 10905 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10906 [30, 8], /* Heshvan */ 10907 [59, 9], /* Kislev */ 10908 [88, 10], /* Teveth */ 10909 [117, 11], /* Shevat */ 10910 [147, 12], /* Adar I */ 10911 [177, 13], /* Adar II */ 10912 [206, 1], /* Nisan */ 10913 [236, 2], /* Iyyar */ 10914 [265, 3], /* Sivan */ 10915 [295, 4], /* Tammuz */ 10916 [324, 5], /* Av */ 10917 [354, 6], /* Elul */ 10918 [384, 7] /* end of year sentinel value */ 10919 ]; 10920 10921 /** 10922 * Number of days difference between RD 0 of the Hebrew calendar 10923 * (Jan 1, 1 Gregorian = JD 1721057.5) and RD 0 of the Hebrew calendar 10924 * (September 7, -3760 Gregorian = JD 347997.25) 10925 * @private 10926 * @const 10927 * @type number 10928 */ 10929 HebrewDate.GregorianDiff = 1373060.25; 10930 10931 /** 10932 * Return a new RD for this date type using the given params. 10933 * @private 10934 * @param {Object=} params the parameters used to create this rata die instance 10935 * @returns {RataDie} the new RD instance for the given params 10936 */ 10937 HebrewDate.prototype.newRd = function (params) { 10938 return new HebrewRataDie(params); 10939 }; 10940 10941 /** 10942 * Return the year for the given RD 10943 * @protected 10944 * @param {number} rd RD to calculate from 10945 * @returns {number} the year for the RD 10946 */ 10947 HebrewDate.prototype._calcYear = function(rd) { 10948 var year, approximation, nextNewYear; 10949 10950 // divide by the average number of days per year in the Hebrew calendar 10951 // to approximate the year, then tweak it to get the real year 10952 approximation = Math.floor(rd / 365.246822206) + 1; 10953 10954 // console.log("HebrewDate._calcYear: approx is " + approximation); 10955 10956 // search forward from approximation-1 for the year that actually contains this rd 10957 year = approximation; 10958 nextNewYear = HebrewCal.newYear(year); 10959 while (rd >= nextNewYear) { 10960 year++; 10961 nextNewYear = HebrewCal.newYear(year); 10962 } 10963 return year - 1; 10964 }; 10965 10966 /** 10967 * Calculate date components for the given RD date. 10968 * @protected 10969 */ 10970 HebrewDate.prototype._calcDateComponents = function () { 10971 var remainder, 10972 i, 10973 table, 10974 target, 10975 rd = this.rd.getRataDie(); 10976 10977 // console.log("HebrewDate.calcComponents: calculating for rd " + rd); 10978 10979 if (typeof(this.offset) === "undefined") { 10980 this.year = this._calcYear(rd); 10981 10982 // now offset the RD by the time zone, then recalculate in case we were 10983 // near the year boundary 10984 if (!this.tz) { 10985 this.tz = new TimeZone({id: this.timezone}); 10986 } 10987 this.offset = this.tz.getOffsetMillis(this) / 86400000; 10988 } 10989 10990 if (this.offset !== 0) { 10991 rd += this.offset; 10992 this.year = this._calcYear(rd); 10993 } 10994 10995 // console.log("HebrewDate.calcComponents: year is " + this.year + " with starting rd " + thisNewYear); 10996 10997 remainder = rd - HebrewCal.newYear(this.year); 10998 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 10999 11000 // take out new years corrections so we get the right month when we look it up in the table 11001 if (remainder >= 59) { 11002 if (remainder >= 88) { 11003 if (HebrewCal.longKislev(this.year)) { 11004 remainder--; 11005 } 11006 } 11007 if (HebrewCal.longHeshvan(this.year)) { 11008 remainder--; 11009 } 11010 } 11011 11012 // console.log("HebrewDate.calcComponents: after new years corrections, remainder is " + remainder); 11013 11014 table = this.cal.isLeapYear(this.year) ? 11015 HebrewDate.cumMonthLengthsLeapReverse : 11016 HebrewDate.cumMonthLengthsReverse; 11017 11018 i = 0; 11019 target = Math.floor(remainder); 11020 while (i+1 < table.length && target >= table[i+1][0]) { 11021 i++; 11022 } 11023 11024 this.month = table[i][1]; 11025 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 11026 remainder -= table[i][0]; 11027 11028 // console.log("HebrewDate.calcComponents: month is " + this.month + " and remainder is " + remainder); 11029 11030 this.day = Math.floor(remainder); 11031 remainder -= this.day; 11032 this.day++; // days are 1-based 11033 11034 // console.log("HebrewDate.calcComponents: day is " + this.day + " and remainder is " + remainder); 11035 11036 // now convert to milliseconds for the rest of the calculation 11037 remainder = Math.round(remainder * 86400000); 11038 11039 this.hour = Math.floor(remainder/3600000); 11040 remainder -= this.hour * 3600000; 11041 11042 // the hours from 0 to 6 are actually 18:00 to midnight of the previous 11043 // gregorian day, so we have to adjust for that 11044 if (this.hour >= 6) { 11045 this.hour -= 6; 11046 } else { 11047 this.hour += 18; 11048 } 11049 11050 this.minute = Math.floor(remainder/60000); 11051 remainder -= this.minute * 60000; 11052 11053 this.second = Math.floor(remainder/1000); 11054 remainder -= this.second * 1000; 11055 11056 this.millisecond = Math.floor(remainder); 11057 }; 11058 11059 /** 11060 * Return the day of the week of this date. The day of the week is encoded 11061 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11062 * 11063 * @return {number} the day of the week 11064 */ 11065 HebrewDate.prototype.getDayOfWeek = function() { 11066 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11067 return MathUtils.mod(rd+1, 7); 11068 }; 11069 11070 /** 11071 * Get the Halaqim (parts) of an hour. There are 1080 parts in an hour, which means 11072 * each part is 3.33333333 seconds long. This means the number returned may not 11073 * be an integer. 11074 * 11075 * @return {number} the halaqim parts of the current hour 11076 */ 11077 HebrewDate.prototype.getHalaqim = function() { 11078 if (this.parts < 0) { 11079 // convert to ms first, then to parts 11080 var h = this.minute * 60000 + this.second * 1000 + this.millisecond; 11081 this.parts = (h * 0.0003); 11082 } 11083 return this.parts; 11084 }; 11085 11086 /** 11087 * Return the rd number of the first Sunday of the given ISO year. 11088 * @protected 11089 * @return the rd of the first Sunday of the ISO year 11090 */ 11091 HebrewDate.prototype.firstSunday = function (year) { 11092 var tishri1 = this.newRd({ 11093 year: year, 11094 month: 7, 11095 day: 1, 11096 hour: 18, 11097 minute: 0, 11098 second: 0, 11099 millisecond: 0, 11100 cal: this.cal 11101 }); 11102 var firstThu = this.newRd({ 11103 rd: tishri1.onOrAfter(4), 11104 cal: this.cal 11105 }); 11106 return firstThu.before(0); 11107 }; 11108 11109 /** 11110 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 11111 * 385, regardless of months or weeks, etc. That is, Tishri 1st is day 1, and 11112 * Elul 29 is 385 for a leap year with a long Heshvan and long Kislev. 11113 * @return {number} the ordinal day of the year 11114 */ 11115 HebrewDate.prototype.getDayOfYear = function() { 11116 var table = this.cal.isLeapYear(this.year) ? 11117 HebrewRataDie.cumMonthLengthsLeap : 11118 HebrewRataDie.cumMonthLengths; 11119 var days = table[this.month-1]; 11120 if ((this.month < 7 || this.month > 8) && HebrewCal.longHeshvan(this.year)) { 11121 days++; 11122 } 11123 if ((this.month < 7 || this.month > 9) && HebrewCal.longKislev(this.year)) { 11124 days++; 11125 } 11126 11127 return days + this.day; 11128 }; 11129 11130 /** 11131 * Return the ordinal number of the week within the month. The first week of a month is 11132 * the first one that contains 4 or more days in that month. If any days precede this 11133 * first week, they are marked as being in week 0. This function returns values from 0 11134 * through 6.<p> 11135 * 11136 * The locale is a required parameter because different locales that use the same 11137 * Hebrew calendar consider different days of the week to be the beginning of 11138 * the week. This can affect the week of the month in which some days are located. 11139 * 11140 * @param {Locale|string} locale the locale or locale spec to use when figuring out 11141 * the first day of the week 11142 * @return {number} the ordinal number of the week within the current month 11143 */ 11144 HebrewDate.prototype.getWeekOfMonth = function(locale) { 11145 var li = new LocaleInfo(locale), 11146 first = this.newRd({ 11147 year: this.year, 11148 month: this.month, 11149 day: 1, 11150 hour: 18, 11151 minute: 0, 11152 second: 0, 11153 millisecond: 0 11154 }), 11155 rd = this.rd.getRataDie(), 11156 weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 11157 11158 if (weekStart - first.getRataDie() > 3) { 11159 // if the first week has 4 or more days in it of the current month, then consider 11160 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 11161 // one week earlier. 11162 weekStart -= 7; 11163 } 11164 return (rd < weekStart) ? 0 : Math.floor((rd - weekStart) / 7) + 1; 11165 }; 11166 11167 /** 11168 * Return the era for this date as a number. The value for the era for Hebrew 11169 * calendars is -1 for "before the Hebrew era" and 1 for "the Hebrew era". 11170 * Hebrew era dates are any date after Tishri 1, 1, which is the same as 11171 * September 7, 3760 BC in the Gregorian calendar. 11172 * 11173 * @return {number} 1 if this date is in the Hebrew era, -1 if it is before the 11174 * Hebrew era 11175 */ 11176 HebrewDate.prototype.getEra = function() { 11177 return (this.year < 1) ? -1 : 1; 11178 }; 11179 11180 /** 11181 * Return the name of the calendar that governs this date. 11182 * 11183 * @return {string} a string giving the name of the calendar 11184 */ 11185 HebrewDate.prototype.getCalendar = function() { 11186 return "hebrew"; 11187 }; 11188 11189 // register with the factory method 11190 IDate._constructors["hebrew"] = HebrewDate; 11191 11192 11193 11194 /*< IslamicCal.js */ 11195 /* 11196 * islamic.js - Represent a Islamic calendar object. 11197 * 11198 * Copyright © 2012-2015, JEDLSoft 11199 * 11200 * Licensed under the Apache License, Version 2.0 (the "License"); 11201 * you may not use this file except in compliance with the License. 11202 * You may obtain a copy of the License at 11203 * 11204 * http://www.apache.org/licenses/LICENSE-2.0 11205 * 11206 * Unless required by applicable law or agreed to in writing, software 11207 * distributed under the License is distributed on an "AS IS" BASIS, 11208 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11209 * 11210 * See the License for the specific language governing permissions and 11211 * limitations under the License. 11212 */ 11213 11214 11215 /* !depends 11216 ilib.js 11217 Calendar.js 11218 MathUtils.js 11219 */ 11220 11221 11222 /** 11223 * @class 11224 * Construct a new Islamic calendar object. This class encodes information about 11225 * the civil Islamic calendar. The civil Islamic calendar is a tabular islamic 11226 * calendar where the dates are calculated by arithmetic rules. This differs from 11227 * the religious Islamic calendar which is used to mark the beginning of particular 11228 * holidays. The religious calendar depends on the first sighting of the new 11229 * crescent moon to determine the first day of the new month. Because humans and 11230 * weather are both involved, the actual time of sighting varies, so it is not 11231 * really possible to precalculate the religious calendar. Certain groups, such 11232 * as the Islamic Society of North America, decreed in in 2007 that they will use 11233 * a calendar based on calculations rather than observations to determine the 11234 * beginning of lunar months, and therefore the dates of holidays.<p> 11235 * 11236 * 11237 * @constructor 11238 * @extends Calendar 11239 */ 11240 var IslamicCal = function() { 11241 this.type = "islamic"; 11242 }; 11243 11244 /** 11245 * the lengths of each month 11246 * @private 11247 * @const 11248 * @type Array.<number> 11249 */ 11250 IslamicCal.monthLengths = [ 11251 30, /* Muharram */ 11252 29, /* Saffar */ 11253 30, /* Rabi'I */ 11254 29, /* Rabi'II */ 11255 30, /* Jumada I */ 11256 29, /* Jumada II */ 11257 30, /* Rajab */ 11258 29, /* Sha'ban */ 11259 30, /* Ramadan */ 11260 29, /* Shawwal */ 11261 30, /* Dhu al-Qa'da */ 11262 29 /* Dhu al-Hijja */ 11263 ]; 11264 11265 11266 /** 11267 * Return the number of months in the given year. The number of months in a year varies 11268 * for luni-solar calendars because in some years, an extra month is needed to extend the 11269 * days in a year to an entire solar year. The month is represented as a 1-based number 11270 * where 1=first month, 2=second month, etc. 11271 * 11272 * @param {number} year a year for which the number of months is sought 11273 */ 11274 IslamicCal.prototype.getNumMonths = function(year) { 11275 return 12; 11276 }; 11277 11278 /** 11279 * Return the number of days in a particular month in a particular year. This function 11280 * can return a different number for a month depending on the year because of things 11281 * like leap years. 11282 * 11283 * @param {number} month the month for which the length is sought 11284 * @param {number} year the year within which that month can be found 11285 * @return {number} the number of days within the given month in the given year 11286 */ 11287 IslamicCal.prototype.getMonLength = function(month, year) { 11288 if (month !== 12) { 11289 return IslamicCal.monthLengths[month-1]; 11290 } else { 11291 return this.isLeapYear(year) ? 30 : 29; 11292 } 11293 }; 11294 11295 /** 11296 * Return true if the given year is a leap year in the Islamic calendar. 11297 * The year parameter may be given as a number, or as a IslamicDate object. 11298 * @param {number} year the year for which the leap year information is being sought 11299 * @return {boolean} true if the given year is a leap year 11300 */ 11301 IslamicCal.prototype.isLeapYear = function(year) { 11302 return (MathUtils.mod((14 + 11 * year), 30) < 11); 11303 }; 11304 11305 /** 11306 * Return the type of this calendar. 11307 * 11308 * @return {string} the name of the type of this calendar 11309 */ 11310 IslamicCal.prototype.getType = function() { 11311 return this.type; 11312 }; 11313 11314 /** 11315 * Return a date instance for this calendar type using the given 11316 * options. 11317 * @param {Object} options options controlling the construction of 11318 * the date instance 11319 * @return {IslamicDate} a date appropriate for this calendar type 11320 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 11321 */ 11322 IslamicCal.prototype.newDateInstance = function (options) { 11323 return new IslamicDate(options); 11324 }; 11325 11326 /*register this calendar for the factory method */ 11327 Calendar._constructors["islamic"] = IslamicCal; 11328 11329 11330 /*< IslamicRataDie.js */ 11331 /* 11332 * IslamicRataDie.js - Represent an RD date in the Islamic calendar 11333 * 11334 * Copyright © 2012-2015, JEDLSoft 11335 * 11336 * Licensed under the Apache License, Version 2.0 (the "License"); 11337 * you may not use this file except in compliance with the License. 11338 * You may obtain a copy of the License at 11339 * 11340 * http://www.apache.org/licenses/LICENSE-2.0 11341 * 11342 * Unless required by applicable law or agreed to in writing, software 11343 * distributed under the License is distributed on an "AS IS" BASIS, 11344 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11345 * 11346 * See the License for the specific language governing permissions and 11347 * limitations under the License. 11348 */ 11349 11350 /* !depends 11351 IslamicCal.js 11352 RataDie.js 11353 */ 11354 11355 11356 /** 11357 * @class 11358 * Construct a new Islamic RD date number object. The constructor parameters can 11359 * contain any of the following properties: 11360 * 11361 * <ul> 11362 * <li><i>unixtime<i> - sets the time of this instance according to the given 11363 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 11364 * 11365 * <li><i>julianday</i> - sets the time of this instance according to the given 11366 * Julian Day instance or the Julian Day given as a float 11367 * 11368 * <li><i>year</i> - any integer, including 0 11369 * 11370 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11371 * 11372 * <li><i>day</i> - 1 to 31 11373 * 11374 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11375 * is always done with an unambiguous 24 hour representation 11376 * 11377 * <li><i>minute</i> - 0 to 59 11378 * 11379 * <li><i>second</i> - 0 to 59 11380 * 11381 * <li><i>millisecond</i> - 0 to 999 11382 * 11383 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11384 * </ul> 11385 * 11386 * If the constructor is called with another Islamic date instance instead of 11387 * a parameter block, the other instance acts as a parameter block and its 11388 * settings are copied into the current instance.<p> 11389 * 11390 * If the constructor is called with no arguments at all or if none of the 11391 * properties listed above are present, then the RD is calculate based on 11392 * the current date at the time of instantiation. <p> 11393 * 11394 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 11395 * specified in the params, it is assumed that they have the smallest possible 11396 * value in the range for the property (zero or one).<p> 11397 * 11398 * 11399 * @private 11400 * @constructor 11401 * @extends RataDie 11402 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic RD date 11403 */ 11404 var IslamicRataDie = function(params) { 11405 this.cal = params && params.cal || new IslamicCal(); 11406 this.rd = NaN; 11407 RataDie.call(this, params); 11408 }; 11409 11410 IslamicRataDie.prototype = new RataDie(); 11411 IslamicRataDie.prototype.parent = RataDie; 11412 IslamicRataDie.prototype.constructor = IslamicRataDie; 11413 11414 /** 11415 * The difference between a zero Julian day and the first Islamic date 11416 * of Friday, July 16, 622 CE Julian. 11417 * @private 11418 * @type number 11419 */ 11420 IslamicRataDie.prototype.epoch = 1948439.5; 11421 11422 /** 11423 * Calculate the Rata Die (fixed day) number of the given date from the 11424 * date components. 11425 * 11426 * @protected 11427 * @param {Object} date the date components to calculate the RD from 11428 */ 11429 IslamicRataDie.prototype._setDateComponents = function(date) { 11430 var days = (date.year - 1) * 354 + 11431 Math.ceil(29.5 * (date.month - 1)) + 11432 date.day + 11433 Math.floor((3 + 11 * date.year) / 30) - 1; 11434 var time = (date.hour * 3600000 + 11435 date.minute * 60000 + 11436 date.second * 1000 + 11437 date.millisecond) / 11438 86400000; 11439 11440 //console.log("getRataDie: converting " + JSON.stringify(date)); 11441 //console.log("getRataDie: days is " + days); 11442 //console.log("getRataDie: time is " + time); 11443 //console.log("getRataDie: rd is " + (days + time)); 11444 11445 this.rd = days + time; 11446 }; 11447 11448 11449 /*< IslamicDate.js */ 11450 /* 11451 * islamicDate.js - Represent a date in the Islamic calendar 11452 * 11453 * Copyright © 2012-2015, JEDLSoft 11454 * 11455 * Licensed under the Apache License, Version 2.0 (the "License"); 11456 * you may not use this file except in compliance with the License. 11457 * You may obtain a copy of the License at 11458 * 11459 * http://www.apache.org/licenses/LICENSE-2.0 11460 * 11461 * Unless required by applicable law or agreed to in writing, software 11462 * distributed under the License is distributed on an "AS IS" BASIS, 11463 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11464 * 11465 * See the License for the specific language governing permissions and 11466 * limitations under the License. 11467 */ 11468 11469 /* !depends 11470 ilib.js 11471 Locale.js 11472 LocaleInfo.js 11473 TimeZone.js 11474 IDate.js 11475 MathUtils.js 11476 SearchUtils.js 11477 Calendar.js 11478 IslamicCal.js 11479 IslamicRataDie.js 11480 */ 11481 11482 11483 11484 11485 /** 11486 * @class 11487 * Construct a new civil Islamic date object. The constructor can be called 11488 * with a params object that can contain the following properties:<p> 11489 * 11490 * <ul> 11491 * <li><i>julianday</i> - the Julian Day to set into this date 11492 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 11493 * <li><i>month</i> - 1 to 12, where 1 means Muharram, 2 means Saffar, etc. 11494 * <li><i>day</i> - 1 to 30 11495 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11496 * is always done with an unambiguous 24 hour representation 11497 * <li><i>minute</i> - 0 to 59 11498 * <li><i>second</i> - 0 to 59 11499 * <li><i>millisecond</i> - 0 to 999 11500 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 11501 * of this julian date. The date/time is kept in the local time. The time zone 11502 * is used later if this date is formatted according to a different time zone and 11503 * the difference has to be calculated, or when the date format has a time zone 11504 * component in it. 11505 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 11506 * given, it can be inferred from this locale. For locales that span multiple 11507 * time zones, the one with the largest population is chosen as the one that 11508 * represents the locale. 11509 * 11510 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11511 * </ul> 11512 * 11513 * If called with another Islamic date argument, the date components of the given 11514 * date are copied into the current one.<p> 11515 * 11516 * If the constructor is called with no arguments at all or if none of the 11517 * properties listed above 11518 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 11519 * components are 11520 * filled in with the current date at the time of instantiation. Note that if 11521 * you do not give the time zone when defaulting to the current time and the 11522 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 11523 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 11524 * Mean Time").<p> 11525 * 11526 * 11527 * @constructor 11528 * @extends IDate 11529 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic date 11530 */ 11531 var IslamicDate = function(params) { 11532 this.cal = new IslamicCal(); 11533 11534 if (params) { 11535 if (params.locale) { 11536 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 11537 var li = new LocaleInfo(this.locale); 11538 this.timezone = li.getTimeZone(); 11539 } 11540 if (params.timezone) { 11541 this.timezone = params.timezone; 11542 } 11543 11544 if (params.year || params.month || params.day || params.hour || 11545 params.minute || params.second || params.millisecond ) { 11546 /** 11547 * Year in the Islamic calendar. 11548 * @type number 11549 */ 11550 this.year = parseInt(params.year, 10) || 0; 11551 11552 /** 11553 * The month number, ranging from 1 to 12 (December). 11554 * @type number 11555 */ 11556 this.month = parseInt(params.month, 10) || 1; 11557 11558 /** 11559 * The day of the month. This ranges from 1 to 30. 11560 * @type number 11561 */ 11562 this.day = parseInt(params.day, 10) || 1; 11563 11564 /** 11565 * The hour of the day. This can be a number from 0 to 23, as times are 11566 * stored unambiguously in the 24-hour clock. 11567 * @type number 11568 */ 11569 this.hour = parseInt(params.hour, 10) || 0; 11570 11571 /** 11572 * The minute of the hours. Ranges from 0 to 59. 11573 * @type number 11574 */ 11575 this.minute = parseInt(params.minute, 10) || 0; 11576 11577 /** 11578 * The second of the minute. Ranges from 0 to 59. 11579 * @type number 11580 */ 11581 this.second = parseInt(params.second, 10) || 0; 11582 11583 /** 11584 * The millisecond of the second. Ranges from 0 to 999. 11585 * @type number 11586 */ 11587 this.millisecond = parseInt(params.millisecond, 10) || 0; 11588 11589 /** 11590 * The day of the year. Ranges from 1 to 355. 11591 * @type number 11592 */ 11593 this.dayOfYear = parseInt(params.dayOfYear, 10); 11594 11595 if (typeof(params.dst) === 'boolean') { 11596 this.dst = params.dst; 11597 } 11598 11599 this.rd = this.newRd(this); 11600 11601 // add the time zone offset to the rd to convert to UTC 11602 if (!this.tz) { 11603 this.tz = new TimeZone({id: this.timezone}); 11604 } 11605 // getOffsetMillis requires that this.year, this.rd, and this.dst 11606 // are set in order to figure out which time zone rules apply and 11607 // what the offset is at that point in the year 11608 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 11609 if (this.offset !== 0) { 11610 this.rd = this.newRd({ 11611 rd: this.rd.getRataDie() - this.offset 11612 }); 11613 } 11614 } 11615 } 11616 11617 if (!this.rd) { 11618 this.rd = this.newRd(params); 11619 this._calcDateComponents(); 11620 } 11621 }; 11622 11623 IslamicDate.prototype = new IDate({noinstance: true}); 11624 IslamicDate.prototype.parent = IDate; 11625 IslamicDate.prototype.constructor = IslamicDate; 11626 11627 /** 11628 * the cumulative lengths of each month, for a non-leap year 11629 * @private 11630 * @const 11631 * @type Array.<number> 11632 */ 11633 IslamicDate.cumMonthLengths = [ 11634 0, /* Muharram */ 11635 30, /* Saffar */ 11636 59, /* Rabi'I */ 11637 89, /* Rabi'II */ 11638 118, /* Jumada I */ 11639 148, /* Jumada II */ 11640 177, /* Rajab */ 11641 207, /* Sha'ban */ 11642 236, /* Ramadan */ 11643 266, /* Shawwal */ 11644 295, /* Dhu al-Qa'da */ 11645 325, /* Dhu al-Hijja */ 11646 354 11647 ]; 11648 11649 /** 11650 * Number of days difference between RD 0 of the Gregorian calendar and 11651 * RD 0 of the Islamic calendar. 11652 * @private 11653 * @const 11654 * @type number 11655 */ 11656 IslamicDate.GregorianDiff = 227015; 11657 11658 /** 11659 * Return a new RD for this date type using the given params. 11660 * @protected 11661 * @param {Object=} params the parameters used to create this rata die instance 11662 * @returns {RataDie} the new RD instance for the given params 11663 */ 11664 IslamicDate.prototype.newRd = function (params) { 11665 return new IslamicRataDie(params); 11666 }; 11667 11668 /** 11669 * Return the year for the given RD 11670 * @protected 11671 * @param {number} rd RD to calculate from 11672 * @returns {number} the year for the RD 11673 */ 11674 IslamicDate.prototype._calcYear = function(rd) { 11675 return Math.floor((30 * rd + 10646) / 10631); 11676 }; 11677 11678 /** 11679 * Calculate date components for the given RD date. 11680 * @protected 11681 */ 11682 IslamicDate.prototype._calcDateComponents = function () { 11683 var remainder, 11684 rd = this.rd.getRataDie(); 11685 11686 this.year = this._calcYear(rd); 11687 11688 if (typeof(this.offset) === "undefined") { 11689 this.year = this._calcYear(rd); 11690 11691 // now offset the RD by the time zone, then recalculate in case we were 11692 // near the year boundary 11693 if (!this.tz) { 11694 this.tz = new TimeZone({id: this.timezone}); 11695 } 11696 this.offset = this.tz.getOffsetMillis(this) / 86400000; 11697 } 11698 11699 if (this.offset !== 0) { 11700 rd += this.offset; 11701 this.year = this._calcYear(rd); 11702 } 11703 11704 //console.log("IslamicDate.calcComponent: calculating for rd " + rd); 11705 //console.log("IslamicDate.calcComponent: year is " + ret.year); 11706 var yearStart = this.newRd({ 11707 year: this.year, 11708 month: 1, 11709 day: 1, 11710 hour: 0, 11711 minute: 0, 11712 second: 0, 11713 millisecond: 0 11714 }); 11715 remainder = rd - yearStart.getRataDie() + 1; 11716 11717 this.dayOfYear = remainder; 11718 11719 //console.log("IslamicDate.calcComponent: remainder is " + remainder); 11720 11721 this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); 11722 remainder -= IslamicDate.cumMonthLengths[this.month-1]; 11723 11724 //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 11725 11726 this.day = Math.floor(remainder); 11727 remainder -= this.day; 11728 11729 //console.log("IslamicDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 11730 11731 // now convert to milliseconds for the rest of the calculation 11732 remainder = Math.round(remainder * 86400000); 11733 11734 this.hour = Math.floor(remainder/3600000); 11735 remainder -= this.hour * 3600000; 11736 11737 this.minute = Math.floor(remainder/60000); 11738 remainder -= this.minute * 60000; 11739 11740 this.second = Math.floor(remainder/1000); 11741 remainder -= this.second * 1000; 11742 11743 this.millisecond = remainder; 11744 }; 11745 11746 /** 11747 * Return the day of the week of this date. The day of the week is encoded 11748 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11749 * 11750 * @return {number} the day of the week 11751 */ 11752 IslamicDate.prototype.getDayOfWeek = function() { 11753 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11754 return MathUtils.mod(rd-2, 7); 11755 }; 11756 11757 /** 11758 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 11759 * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and 11760 * Dhu al-Hijja 29 is 354. 11761 * @return {number} the ordinal day of the year 11762 */ 11763 IslamicDate.prototype.getDayOfYear = function() { 11764 return IslamicDate.cumMonthLengths[this.month-1] + this.day; 11765 }; 11766 11767 /** 11768 * Return the era for this date as a number. The value for the era for Islamic 11769 * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". 11770 * Islamic era dates are any date after Muharran 1, 1, which is the same as 11771 * July 16, 622 CE in the Gregorian calendar. 11772 * 11773 * @return {number} 1 if this date is in the common era, -1 if it is before the 11774 * common era 11775 */ 11776 IslamicDate.prototype.getEra = function() { 11777 return (this.year < 1) ? -1 : 1; 11778 }; 11779 11780 /** 11781 * Return the name of the calendar that governs this date. 11782 * 11783 * @return {string} a string giving the name of the calendar 11784 */ 11785 IslamicDate.prototype.getCalendar = function() { 11786 return "islamic"; 11787 }; 11788 11789 //register with the factory method 11790 IDate._constructors["islamic"] = IslamicDate; 11791 11792 11793 /*< JulianCal.js */ 11794 /* 11795 * julian.js - Represent a Julian calendar object. 11796 * 11797 * Copyright © 2012-2015, JEDLSoft 11798 * 11799 * Licensed under the Apache License, Version 2.0 (the "License"); 11800 * you may not use this file except in compliance with the License. 11801 * You may obtain a copy of the License at 11802 * 11803 * http://www.apache.org/licenses/LICENSE-2.0 11804 * 11805 * Unless required by applicable law or agreed to in writing, software 11806 * distributed under the License is distributed on an "AS IS" BASIS, 11807 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11808 * 11809 * See the License for the specific language governing permissions and 11810 * limitations under the License. 11811 */ 11812 11813 11814 /* !depends ilib.js Calendar.js MathUtils.js */ 11815 11816 11817 /** 11818 * @class 11819 * Construct a new Julian calendar object. This class encodes information about 11820 * a Julian calendar.<p> 11821 * 11822 * 11823 * @constructor 11824 * @extends Calendar 11825 */ 11826 var JulianCal = function() { 11827 this.type = "julian"; 11828 }; 11829 11830 /* the lengths of each month */ 11831 JulianCal.monthLengths = [ 11832 31, /* Jan */ 11833 28, /* Feb */ 11834 31, /* Mar */ 11835 30, /* Apr */ 11836 31, /* May */ 11837 30, /* Jun */ 11838 31, /* Jul */ 11839 31, /* Aug */ 11840 30, /* Sep */ 11841 31, /* Oct */ 11842 30, /* Nov */ 11843 31 /* Dec */ 11844 ]; 11845 11846 /** 11847 * the cumulative lengths of each month, for a non-leap year 11848 * @private 11849 * @const 11850 * @type Array.<number> 11851 */ 11852 JulianCal.cumMonthLengths = [ 11853 0, /* Jan */ 11854 31, /* Feb */ 11855 59, /* Mar */ 11856 90, /* Apr */ 11857 120, /* May */ 11858 151, /* Jun */ 11859 181, /* Jul */ 11860 212, /* Aug */ 11861 243, /* Sep */ 11862 273, /* Oct */ 11863 304, /* Nov */ 11864 334, /* Dec */ 11865 365 11866 ]; 11867 11868 /** 11869 * the cumulative lengths of each month, for a leap year 11870 * @private 11871 * @const 11872 * @type Array.<number> 11873 */ 11874 JulianCal.cumMonthLengthsLeap = [ 11875 0, /* Jan */ 11876 31, /* Feb */ 11877 60, /* Mar */ 11878 91, /* Apr */ 11879 121, /* May */ 11880 152, /* Jun */ 11881 182, /* Jul */ 11882 213, /* Aug */ 11883 244, /* Sep */ 11884 274, /* Oct */ 11885 305, /* Nov */ 11886 335, /* Dec */ 11887 366 11888 ]; 11889 11890 /** 11891 * Return the number of months in the given year. The number of months in a year varies 11892 * for lunar calendars because in some years, an extra month is needed to extend the 11893 * days in a year to an entire solar year. The month is represented as a 1-based number 11894 * where 1=Jaunary, 2=February, etc. until 12=December. 11895 * 11896 * @param {number} year a year for which the number of months is sought 11897 */ 11898 JulianCal.prototype.getNumMonths = function(year) { 11899 return 12; 11900 }; 11901 11902 /** 11903 * Return the number of days in a particular month in a particular year. This function 11904 * can return a different number for a month depending on the year because of things 11905 * like leap years. 11906 * 11907 * @param {number} month the month for which the length is sought 11908 * @param {number} year the year within which that month can be found 11909 * @return {number} the number of days within the given month in the given year 11910 */ 11911 JulianCal.prototype.getMonLength = function(month, year) { 11912 if (month !== 2 || !this.isLeapYear(year)) { 11913 return JulianCal.monthLengths[month-1]; 11914 } else { 11915 return 29; 11916 } 11917 }; 11918 11919 /** 11920 * Return true if the given year is a leap year in the Julian calendar. 11921 * The year parameter may be given as a number, or as a JulDate object. 11922 * @param {number|JulianDate} year the year for which the leap year information is being sought 11923 * @return {boolean} true if the given year is a leap year 11924 */ 11925 JulianCal.prototype.isLeapYear = function(year) { 11926 var y = (typeof(year) === 'number' ? year : year.year); 11927 return MathUtils.mod(y, 4) === ((year > 0) ? 0 : 3); 11928 }; 11929 11930 /** 11931 * Return the type of this calendar. 11932 * 11933 * @return {string} the name of the type of this calendar 11934 */ 11935 JulianCal.prototype.getType = function() { 11936 return this.type; 11937 }; 11938 11939 /** 11940 * Return a date instance for this calendar type using the given 11941 * options. 11942 * @param {Object} options options controlling the construction of 11943 * the date instance 11944 * @return {IDate} a date appropriate for this calendar type 11945 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 11946 */ 11947 JulianCal.prototype.newDateInstance = function (options) { 11948 return new JulianDate(options); 11949 }; 11950 11951 /* register this calendar for the factory method */ 11952 Calendar._constructors["julian"] = JulianCal; 11953 11954 11955 /*< JulianRataDie.js */ 11956 /* 11957 * julianDate.js - Represent a date in the Julian calendar 11958 * 11959 * Copyright © 2012-2015, JEDLSoft 11960 * 11961 * Licensed under the Apache License, Version 2.0 (the "License"); 11962 * you may not use this file except in compliance with the License. 11963 * You may obtain a copy of the License at 11964 * 11965 * http://www.apache.org/licenses/LICENSE-2.0 11966 * 11967 * Unless required by applicable law or agreed to in writing, software 11968 * distributed under the License is distributed on an "AS IS" BASIS, 11969 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11970 * 11971 * See the License for the specific language governing permissions and 11972 * limitations under the License. 11973 */ 11974 11975 /* !depends 11976 JulianCal.js 11977 RataDie.js 11978 */ 11979 11980 11981 /** 11982 * @class 11983 * Construct a new Julian RD date number object. The constructor parameters can 11984 * contain any of the following properties: 11985 * 11986 * <ul> 11987 * <li><i>unixtime<i> - sets the time of this instance according to the given 11988 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 11989 * 11990 * <li><i>julianday</i> - sets the time of this instance according to the given 11991 * Julian Day instance or the Julian Day given as a float 11992 * 11993 * <li><i>year</i> - any integer, including 0 11994 * 11995 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11996 * 11997 * <li><i>day</i> - 1 to 31 11998 * 11999 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12000 * is always done with an unambiguous 24 hour representation 12001 * 12002 * <li><i>minute</i> - 0 to 59 12003 * 12004 * <li><i>second</i> - 0 to 59 12005 * 12006 * <li><i>millisecond</i> - 0 to 999 12007 * 12008 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 12009 * </ul> 12010 * 12011 * If the constructor is called with another Julian date instance instead of 12012 * a parameter block, the other instance acts as a parameter block and its 12013 * settings are copied into the current instance.<p> 12014 * 12015 * If the constructor is called with no arguments at all or if none of the 12016 * properties listed above are present, then the RD is calculate based on 12017 * the current date at the time of instantiation. <p> 12018 * 12019 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12020 * specified in the params, it is assumed that they have the smallest possible 12021 * value in the range for the property (zero or one).<p> 12022 * 12023 * 12024 * @private 12025 * @constructor 12026 * @extends RataDie 12027 * @param {Object=} params parameters that govern the settings and behaviour of this Julian RD date 12028 */ 12029 var JulianRataDie = function(params) { 12030 this.cal = params && params.cal || new JulianCal(); 12031 this.rd = NaN; 12032 RataDie.call(this, params); 12033 }; 12034 12035 JulianRataDie.prototype = new RataDie(); 12036 JulianRataDie.prototype.parent = RataDie; 12037 JulianRataDie.prototype.constructor = JulianRataDie; 12038 12039 /** 12040 * The difference between a zero Julian day and the first Julian date 12041 * of Friday, July 16, 622 CE Julian. 12042 * @private 12043 * @type number 12044 */ 12045 JulianRataDie.prototype.epoch = 1721422.5; 12046 12047 /** 12048 * Calculate the Rata Die (fixed day) number of the given date from the 12049 * date components. 12050 * 12051 * @protected 12052 * @param {Object} date the date components to calculate the RD from 12053 */ 12054 JulianRataDie.prototype._setDateComponents = function(date) { 12055 var year = date.year + ((date.year < 0) ? 1 : 0); 12056 var years = 365 * (year - 1) + Math.floor((year-1)/4); 12057 var dayInYear = (date.month > 1 ? JulianCal.cumMonthLengths[date.month-1] : 0) + 12058 date.day + 12059 (this.cal.isLeapYear(date.year) && date.month > 2 ? 1 : 0); 12060 var rdtime = (date.hour * 3600000 + 12061 date.minute * 60000 + 12062 date.second * 1000 + 12063 date.millisecond) / 12064 86400000; 12065 12066 /* 12067 console.log("calcRataDie: converting " + JSON.stringify(parts)); 12068 console.log("getRataDie: year is " + years); 12069 console.log("getRataDie: day in year is " + dayInYear); 12070 console.log("getRataDie: rdtime is " + rdtime); 12071 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 12072 */ 12073 12074 this.rd = years + dayInYear + rdtime; 12075 }; 12076 12077 12078 /*< JulianDate.js */ 12079 /* 12080 * JulianDate.js - Represent a date in the Julian calendar 12081 * 12082 * Copyright © 2012-2015, JEDLSoft 12083 * 12084 * Licensed under the Apache License, Version 2.0 (the "License"); 12085 * you may not use this file except in compliance with the License. 12086 * You may obtain a copy of the License at 12087 * 12088 * http://www.apache.org/licenses/LICENSE-2.0 12089 * 12090 * Unless required by applicable law or agreed to in writing, software 12091 * distributed under the License is distributed on an "AS IS" BASIS, 12092 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12093 * 12094 * See the License for the specific language governing permissions and 12095 * limitations under the License. 12096 */ 12097 12098 /* !depends 12099 ilib.js 12100 Locale.js 12101 IDate.js 12102 TimeZone.js 12103 Calendar.js 12104 JulianCal.js 12105 SearchUtils.js 12106 MathUtils.js 12107 LocaleInfo.js 12108 JulianRataDie.js 12109 */ 12110 12111 12112 12113 12114 /** 12115 * @class 12116 * Construct a new date object for the Julian Calendar. The constructor can be called 12117 * with a parameter object that contains any of the following properties: 12118 * 12119 * <ul> 12120 * <li><i>unixtime<i> - sets the time of this instance according to the given 12121 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 12122 * <li><i>julianday</i> - the Julian Day to set into this date 12123 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero 12124 * year which doesn't exist in the Julian calendar 12125 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12126 * <li><i>day</i> - 1 to 31 12127 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12128 * is always done with an unambiguous 24 hour representation 12129 * <li><i>minute</i> - 0 to 59 12130 * <li><i>second</i> - 0 to 59 12131 * <li><i>millisecond<i> - 0 to 999 12132 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 12133 * of this julian date. The date/time is kept in the local time. The time zone 12134 * is used later if this date is formatted according to a different time zone and 12135 * the difference has to be calculated, or when the date format has a time zone 12136 * component in it. 12137 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 12138 * given, it can be inferred from this locale. For locales that span multiple 12139 * time zones, the one with the largest population is chosen as the one that 12140 * represents the locale. 12141 * 12142 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 12143 * </ul> 12144 * 12145 * NB. The <a href="http://en.wikipedia.org/wiki/Julian_date">Julian Day</a> 12146 * (JulianDay) object is a <i>different</i> object than a 12147 * <a href="http://en.wikipedia.org/wiki/Julian_calendar">date in 12148 * the Julian calendar</a> and the two are not to be confused. The Julian Day 12149 * object represents time as a number of whole and fractional days since the 12150 * beginning of the epoch, whereas a date in the Julian 12151 * calendar is a regular date that signifies year, month, day, etc. using the rules 12152 * of the Julian calendar. The naming of Julian Days and the Julian calendar are 12153 * unfortunately close, and come from history.<p> 12154 * 12155 * If called with another Julian date argument, the date components of the given 12156 * date are copied into the current one.<p> 12157 * 12158 * If the constructor is called with no arguments at all or if none of the 12159 * properties listed above 12160 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12161 * components are 12162 * filled in with the current date at the time of instantiation. Note that if 12163 * you do not give the time zone when defaulting to the current time and the 12164 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12165 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12166 * Mean Time").<p> 12167 * 12168 * 12169 * @constructor 12170 * @extends IDate 12171 * @param {Object=} params parameters that govern the settings and behaviour of this Julian date 12172 */ 12173 var JulianDate = function(params) { 12174 this.cal = new JulianCal(); 12175 12176 if (params) { 12177 if (params.locale) { 12178 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 12179 var li = new LocaleInfo(this.locale); 12180 this.timezone = li.getTimeZone(); 12181 } 12182 if (params.timezone) { 12183 this.timezone = params.timezone; 12184 } 12185 12186 if (params.year || params.month || params.day || params.hour || 12187 params.minute || params.second || params.millisecond ) { 12188 /** 12189 * Year in the Julian calendar. 12190 * @type number 12191 */ 12192 this.year = parseInt(params.year, 10) || 0; 12193 /** 12194 * The month number, ranging from 1 (January) to 12 (December). 12195 * @type number 12196 */ 12197 this.month = parseInt(params.month, 10) || 1; 12198 /** 12199 * The day of the month. This ranges from 1 to 31. 12200 * @type number 12201 */ 12202 this.day = parseInt(params.day, 10) || 1; 12203 /** 12204 * The hour of the day. This can be a number from 0 to 23, as times are 12205 * stored unambiguously in the 24-hour clock. 12206 * @type number 12207 */ 12208 this.hour = parseInt(params.hour, 10) || 0; 12209 /** 12210 * The minute of the hours. Ranges from 0 to 59. 12211 * @type number 12212 */ 12213 this.minute = parseInt(params.minute, 10) || 0; 12214 /** 12215 * The second of the minute. Ranges from 0 to 59. 12216 * @type number 12217 */ 12218 this.second = parseInt(params.second, 10) || 0; 12219 /** 12220 * The millisecond of the second. Ranges from 0 to 999. 12221 * @type number 12222 */ 12223 this.millisecond = parseInt(params.millisecond, 10) || 0; 12224 12225 /** 12226 * The day of the year. Ranges from 1 to 383. 12227 * @type number 12228 */ 12229 this.dayOfYear = parseInt(params.dayOfYear, 10); 12230 12231 if (typeof(params.dst) === 'boolean') { 12232 this.dst = params.dst; 12233 } 12234 12235 this.rd = this.newRd(this); 12236 12237 // add the time zone offset to the rd to convert to UTC 12238 if (!this.tz) { 12239 this.tz = new TimeZone({id: this.timezone}); 12240 } 12241 // getOffsetMillis requires that this.year, this.rd, and this.dst 12242 // are set in order to figure out which time zone rules apply and 12243 // what the offset is at that point in the year 12244 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 12245 if (this.offset !== 0) { 12246 this.rd = this.newRd({ 12247 rd: this.rd.getRataDie() - this.offset 12248 }); 12249 } 12250 } 12251 } 12252 12253 if (!this.rd) { 12254 this.rd = this.newRd(params); 12255 this._calcDateComponents(); 12256 } 12257 }; 12258 12259 JulianDate.prototype = new IDate({noinstance: true}); 12260 JulianDate.prototype.parent = IDate; 12261 JulianDate.prototype.constructor = JulianDate; 12262 12263 /** 12264 * Return a new RD for this date type using the given params. 12265 * @protected 12266 * @param {Object=} params the parameters used to create this rata die instance 12267 * @returns {RataDie} the new RD instance for the given params 12268 */ 12269 JulianDate.prototype.newRd = function (params) { 12270 return new JulianRataDie(params); 12271 }; 12272 12273 /** 12274 * Return the year for the given RD 12275 * @protected 12276 * @param {number} rd RD to calculate from 12277 * @returns {number} the year for the RD 12278 */ 12279 JulianDate.prototype._calcYear = function(rd) { 12280 var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); 12281 12282 return (year <= 0) ? year - 1 : year; 12283 }; 12284 12285 /** 12286 * Calculate date components for the given RD date. 12287 * @protected 12288 */ 12289 JulianDate.prototype._calcDateComponents = function () { 12290 var remainder, 12291 cumulative, 12292 rd = this.rd.getRataDie(); 12293 12294 this.year = this._calcYear(rd); 12295 12296 if (typeof(this.offset) === "undefined") { 12297 this.year = this._calcYear(rd); 12298 12299 // now offset the RD by the time zone, then recalculate in case we were 12300 // near the year boundary 12301 if (!this.tz) { 12302 this.tz = new TimeZone({id: this.timezone}); 12303 } 12304 this.offset = this.tz.getOffsetMillis(this) / 86400000; 12305 } 12306 12307 if (this.offset !== 0) { 12308 rd += this.offset; 12309 this.year = this._calcYear(rd); 12310 } 12311 12312 var jan1 = this.newRd({ 12313 year: this.year, 12314 month: 1, 12315 day: 1, 12316 hour: 0, 12317 minute: 0, 12318 second: 0, 12319 millisecond: 0 12320 }); 12321 remainder = rd + 1 - jan1.getRataDie(); 12322 12323 cumulative = this.cal.isLeapYear(this.year) ? 12324 JulianCal.cumMonthLengthsLeap : 12325 JulianCal.cumMonthLengths; 12326 12327 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 12328 remainder = remainder - cumulative[this.month-1]; 12329 12330 this.day = Math.floor(remainder); 12331 remainder -= this.day; 12332 // now convert to milliseconds for the rest of the calculation 12333 remainder = Math.round(remainder * 86400000); 12334 12335 this.hour = Math.floor(remainder/3600000); 12336 remainder -= this.hour * 3600000; 12337 12338 this.minute = Math.floor(remainder/60000); 12339 remainder -= this.minute * 60000; 12340 12341 this.second = Math.floor(remainder/1000); 12342 remainder -= this.second * 1000; 12343 12344 this.millisecond = remainder; 12345 }; 12346 12347 /** 12348 * Return the day of the week of this date. The day of the week is encoded 12349 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 12350 * 12351 * @return {number} the day of the week 12352 */ 12353 JulianDate.prototype.getDayOfWeek = function() { 12354 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 12355 return MathUtils.mod(rd-2, 7); 12356 }; 12357 12358 /** 12359 * Return the name of the calendar that governs this date. 12360 * 12361 * @return {string} a string giving the name of the calendar 12362 */ 12363 JulianDate.prototype.getCalendar = function() { 12364 return "julian"; 12365 }; 12366 12367 //register with the factory method 12368 IDate._constructors["julian"] = JulianDate; 12369 12370 12371 /*< ThaiSolarCal.js */ 12372 /* 12373 * thaisolar.js - Represent a Thai solar calendar object. 12374 * 12375 * Copyright © 2013-2015, JEDLSoft 12376 * 12377 * Licensed under the Apache License, Version 2.0 (the "License"); 12378 * you may not use this file except in compliance with the License. 12379 * You may obtain a copy of the License at 12380 * 12381 * http://www.apache.org/licenses/LICENSE-2.0 12382 * 12383 * Unless required by applicable law or agreed to in writing, software 12384 * distributed under the License is distributed on an "AS IS" BASIS, 12385 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12386 * 12387 * See the License for the specific language governing permissions and 12388 * limitations under the License. 12389 */ 12390 12391 12392 /* !depends ilib.js Calendar.js GregorianCal.js MathUtils.js */ 12393 12394 12395 /** 12396 * @class 12397 * Construct a new Thai solar calendar object. This class encodes information about 12398 * a Thai solar calendar.<p> 12399 * 12400 * 12401 * @constructor 12402 * @extends Calendar 12403 */ 12404 var ThaiSolarCal = function() { 12405 this.type = "thaisolar"; 12406 }; 12407 12408 ThaiSolarCal.prototype = new GregorianCal({noinstance: true}); 12409 ThaiSolarCal.prototype.parent = GregorianCal; 12410 ThaiSolarCal.prototype.constructor = ThaiSolarCal; 12411 12412 /** 12413 * Return true if the given year is a leap year in the Thai solar calendar. 12414 * The year parameter may be given as a number, or as a ThaiSolarDate object. 12415 * @param {number|ThaiSolarDate} year the year for which the leap year information is being sought 12416 * @return {boolean} true if the given year is a leap year 12417 */ 12418 ThaiSolarCal.prototype.isLeapYear = function(year) { 12419 var y = (typeof(year) === 'number' ? year : year.getYears()); 12420 y -= 543; 12421 var centuries = MathUtils.mod(y, 400); 12422 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 12423 }; 12424 12425 /** 12426 * Return a date instance for this calendar type using the given 12427 * options. 12428 * @param {Object} options options controlling the construction of 12429 * the date instance 12430 * @return {IDate} a date appropriate for this calendar type 12431 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 12432 */ 12433 ThaiSolarCal.prototype.newDateInstance = function (options) { 12434 return new ThaiSolarDate(options); 12435 }; 12436 12437 /* register this calendar for the factory method */ 12438 Calendar._constructors["thaisolar"] = ThaiSolarCal; 12439 12440 12441 /*< ThaiSolarDate.js */ 12442 /* 12443 * ThaiSolarDate.js - Represent a date in the ThaiSolar calendar 12444 * 12445 * Copyright © 2013-2015, JEDLSoft 12446 * 12447 * Licensed under the Apache License, Version 2.0 (the "License"); 12448 * you may not use this file except in compliance with the License. 12449 * You may obtain a copy of the License at 12450 * 12451 * http://www.apache.org/licenses/LICENSE-2.0 12452 * 12453 * Unless required by applicable law or agreed to in writing, software 12454 * distributed under the License is distributed on an "AS IS" BASIS, 12455 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12456 * 12457 * See the License for the specific language governing permissions and 12458 * limitations under the License. 12459 */ 12460 12461 /* !depends 12462 ilib.js 12463 IDate.js 12464 JSUtils.js 12465 GregorianDate.js 12466 ThaiSolarCal.js 12467 */ 12468 12469 12470 12471 12472 /** 12473 * @class 12474 * Construct a new Thai solar date object. The constructor parameters can 12475 * contain any of the following properties: 12476 * 12477 * <ul> 12478 * <li><i>unixtime<i> - sets the time of this instance according to the given 12479 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 12480 * 12481 * <li><i>julianday</i> - sets the time of this instance according to the given 12482 * Julian Day instance or the Julian Day given as a float 12483 * 12484 * <li><i>year</i> - any integer, including 0 12485 * 12486 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12487 * 12488 * <li><i>day</i> - 1 to 31 12489 * 12490 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12491 * is always done with an unambiguous 24 hour representation 12492 * 12493 * <li><i>minute</i> - 0 to 59 12494 * 12495 * <li><i>second</i> - 0 to 59 12496 * 12497 * <li><i>millisecond</i> - 0 to 999 12498 * 12499 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 12500 * of this Thai solar date. The date/time is kept in the local time. The time zone 12501 * is used later if this date is formatted according to a different time zone and 12502 * the difference has to be calculated, or when the date format has a time zone 12503 * component in it. 12504 * 12505 * <li><i>locale</i> - locale for this Thai solar date. If the time zone is not 12506 * given, it can be inferred from this locale. For locales that span multiple 12507 * time zones, the one with the largest population is chosen as the one that 12508 * represents the locale. 12509 * </ul> 12510 * 12511 * If the constructor is called with another Thai solar date instance instead of 12512 * a parameter block, the other instance acts as a parameter block and its 12513 * settings are copied into the current instance.<p> 12514 * 12515 * If the constructor is called with no arguments at all or if none of the 12516 * properties listed above 12517 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12518 * components are 12519 * filled in with the current date at the time of instantiation. Note that if 12520 * you do not give the time zone when defaulting to the current time and the 12521 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12522 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12523 * Mean Time").<p> 12524 * 12525 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12526 * specified in the params, it is assumed that they have the smallest possible 12527 * value in the range for the property (zero or one).<p> 12528 * 12529 * 12530 * @constructor 12531 * @extends GregorianDate 12532 * @param {Object=} params parameters that govern the settings and behaviour of this Thai solar date 12533 */ 12534 var ThaiSolarDate = function(params) { 12535 var p = params; 12536 if (params) { 12537 // there is 198327 days difference between the Thai solar and 12538 // Gregorian epochs which is equivalent to 543 years 12539 p = {}; 12540 JSUtils.shallowCopy(params, p); 12541 if (typeof(p.year) !== 'undefined') { 12542 p.year -= 543; 12543 } 12544 if (typeof(p.rd) !== 'undefined') { 12545 p.rd -= 198327; 12546 } 12547 } 12548 this.rd = NaN; // clear these out so that the GregorianDate constructor can set it 12549 this.offset = undefined; 12550 //console.log("ThaiSolarDate.constructor: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12551 GregorianDate.call(this, p); 12552 this.cal = new ThaiSolarCal(); 12553 // make sure the year is set correctly 12554 if (params && typeof(params.year) !== 'undefined') { 12555 this.year = parseInt(params.year, 10); 12556 } 12557 }; 12558 12559 ThaiSolarDate.prototype = new GregorianDate({noinstance: true}); 12560 ThaiSolarDate.prototype.parent = GregorianDate.prototype; 12561 ThaiSolarDate.prototype.constructor = ThaiSolarDate; 12562 12563 /** 12564 * the difference between a zero Julian day and the zero Thai Solar date. 12565 * This is some 543 years before the start of the Gregorian epoch. 12566 * @private 12567 * @type number 12568 */ 12569 ThaiSolarDate.epoch = 1523097.5; 12570 12571 /** 12572 * Calculate the date components for the current time zone 12573 * @protected 12574 */ 12575 ThaiSolarDate.prototype._calcDateComponents = function () { 12576 // there is 198327 days difference between the Thai solar and 12577 // Gregorian epochs which is equivalent to 543 years 12578 // console.log("ThaiSolarDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12579 this.parent._calcDateComponents.call(this); 12580 this.year += 543; 12581 }; 12582 12583 /** 12584 * Return the Rata Die (fixed day) number of this date. 12585 * 12586 * @protected 12587 * @return {number} the rd date as a number 12588 */ 12589 ThaiSolarDate.prototype.getRataDie = function() { 12590 // there is 198327 days difference between the Thai solar and 12591 // Gregorian epochs which is equivalent to 543 years 12592 return this.rd.getRataDie() + 198327; 12593 }; 12594 12595 /** 12596 * Return a new Gregorian date instance that represents the first instance of the 12597 * given day of the week before the current date. The day of the week is encoded 12598 * as a number where 0 = Sunday, 1 = Monday, etc. 12599 * 12600 * @param {number} dow the day of the week before the current date that is being sought 12601 * @return {IDate} the date being sought 12602 */ 12603 ThaiSolarDate.prototype.before = function (dow) { 12604 return new ThaiSolarDate({ 12605 rd: this.rd.before(dow, this.offset) + 198327, 12606 timezone: this.timezone 12607 }); 12608 }; 12609 12610 /** 12611 * Return a new Gregorian date instance that represents the first instance of the 12612 * given day of the week after the current date. The day of the week is encoded 12613 * as a number where 0 = Sunday, 1 = Monday, etc. 12614 * 12615 * @param {number} dow the day of the week after the current date that is being sought 12616 * @return {IDate} the date being sought 12617 */ 12618 ThaiSolarDate.prototype.after = function (dow) { 12619 return new ThaiSolarDate({ 12620 rd: this.rd.after(dow, this.offset) + 198327, 12621 timezone: this.timezone 12622 }); 12623 }; 12624 12625 /** 12626 * Return a new Gregorian date instance that represents the first instance of the 12627 * given day of the week on or before the current date. The day of the week is encoded 12628 * as a number where 0 = Sunday, 1 = Monday, etc. 12629 * 12630 * @param {number} dow the day of the week on or before the current date that is being sought 12631 * @return {IDate} the date being sought 12632 */ 12633 ThaiSolarDate.prototype.onOrBefore = function (dow) { 12634 return new ThaiSolarDate({ 12635 rd: this.rd.onOrBefore(dow, this.offset) + 198327, 12636 timezone: this.timezone 12637 }); 12638 }; 12639 12640 /** 12641 * Return a new Gregorian date instance that represents the first instance of the 12642 * given day of the week on or after the current date. The day of the week is encoded 12643 * as a number where 0 = Sunday, 1 = Monday, etc. 12644 * 12645 * @param {number} dow the day of the week on or after the current date that is being sought 12646 * @return {IDate} the date being sought 12647 */ 12648 ThaiSolarDate.prototype.onOrAfter = function (dow) { 12649 return new ThaiSolarDate({ 12650 rd: this.rd.onOrAfter(dow, this.offset) + 198327, 12651 timezone: this.timezone 12652 }); 12653 }; 12654 12655 /** 12656 * Return the name of the calendar that governs this date. 12657 * 12658 * @return {string} a string giving the name of the calendar 12659 */ 12660 ThaiSolarDate.prototype.getCalendar = function() { 12661 return "thaisolar"; 12662 }; 12663 12664 //register with the factory method 12665 IDate._constructors["thaisolar"] = ThaiSolarDate; 12666 12667 12668 12669 /*< Astro.js */ 12670 /* 12671 * astro.js - Static functions to support astronomical calculations 12672 * 12673 * Copyright © 2014-2015, JEDLSoft 12674 * 12675 * Licensed under the Apache License, Version 2.0 (the "License"); 12676 * you may not use this file except in compliance with the License. 12677 * You may obtain a copy of the License at 12678 * 12679 * http://www.apache.org/licenses/LICENSE-2.0 12680 * 12681 * Unless required by applicable law or agreed to in writing, software 12682 * distributed under the License is distributed on an "AS IS" BASIS, 12683 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12684 * 12685 * See the License for the specific language governing permissions and 12686 * limitations under the License. 12687 */ 12688 12689 /* !depends 12690 ilib.js 12691 IDate.js 12692 Utils.js 12693 MathUtils.js 12694 SearchUtils.js 12695 GregorianDate.js 12696 GregRataDie.js 12697 */ 12698 12699 // !data astro 12700 12701 /* 12702 * These routines were derived from a public domain set of JavaScript 12703 * functions for positional astronomy by John Walker of Fourmilab, 12704 * September 1999. 12705 */ 12706 12707 12708 12709 var Astro = {}; 12710 12711 /** 12712 * Load in all the data needed for astrological calculations. 12713 * 12714 * @private 12715 * @param {boolean} sync 12716 * @param {*} loadParams 12717 * @param {function(*)|undefined} callback 12718 */ 12719 Astro.initAstro = function(sync, loadParams, callback) { 12720 if (!ilib.data.astro) { 12721 Utils.loadData({ 12722 name: "astro.json", // countries in their own language 12723 locale: "-", // only need to load the root file 12724 nonLocale: true, 12725 sync: sync, 12726 loadParams: loadParams, 12727 callback: ilib.bind(this, function(astroData) { 12728 /** 12729 * @type {{ 12730 * _EquinoxpTerms:Array.<number>, 12731 * _JDE0tab1000:Array.<number>, 12732 * _JDE0tab2000:Array.<number>, 12733 * _deltaTtab:Array.<number>, 12734 * _oterms:Array.<number>, 12735 * _nutArgMult:Array.<number>, 12736 * _nutArgCoeff:Array.<number>, 12737 * _nutCoeffA:Array.<number>, 12738 * _nutCoeffB:Array.<number>, 12739 * _coeff19th:Array.<number>, 12740 * _coeff18th:Array.<number>, 12741 * _solarLongCoeff:Array.<number>, 12742 * _solarLongMultipliers:Array.<number>, 12743 * _solarLongAddends:Array.<number>, 12744 * _meanMoonCoeff:Array.<number>, 12745 * _elongationCoeff:Array.<number>, 12746 * _solarAnomalyCoeff:Array.<number>, 12747 * _lunarAnomalyCoeff:Array.<number>, 12748 * _moonFromNodeCoeff:Array.<number>, 12749 * _eCoeff:Array.<number>, 12750 * _lunarElongationLongCoeff:Array.<number>, 12751 * _solarAnomalyLongCoeff:Array.<number>, 12752 * _lunarAnomalyLongCoeff:Array.<number>, 12753 * _moonFromNodeLongCoeff:Array.<number>, 12754 * _sineCoeff:Array.<number>, 12755 * _nmApproxCoeff:Array.<number>, 12756 * _nmCapECoeff:Array.<number>, 12757 * _nmSolarAnomalyCoeff:Array.<number>, 12758 * _nmLunarAnomalyCoeff:Array.<number>, 12759 * _nmMoonArgumentCoeff:Array.<number>, 12760 * _nmCapOmegaCoeff:Array.<number>, 12761 * _nmEFactor:Array.<number>, 12762 * _nmSolarCoeff:Array.<number>, 12763 * _nmLunarCoeff:Array.<number>, 12764 * _nmMoonCoeff:Array.<number>, 12765 * _nmSineCoeff:Array.<number>, 12766 * _nmAddConst:Array.<number>, 12767 * _nmAddCoeff:Array.<number>, 12768 * _nmAddFactor:Array.<number>, 12769 * _nmExtra:Array.<number> 12770 * }} 12771 */ 12772 ilib.data.astro = astroData; 12773 if (callback && typeof(callback) === 'function') { 12774 callback(astroData); 12775 } 12776 }) 12777 }); 12778 } else { 12779 if (callback && typeof(callback) === 'function') { 12780 callback(ilib.data.astro); 12781 } 12782 } 12783 }; 12784 12785 /** 12786 * Convert degrees to radians. 12787 * 12788 * @static 12789 * @protected 12790 * @param {number} d angle in degrees 12791 * @return {number} angle in radians 12792 */ 12793 Astro._dtr = function(d) { 12794 return (d * Math.PI) / 180.0; 12795 }; 12796 12797 /** 12798 * Convert radians to degrees. 12799 * 12800 * @static 12801 * @protected 12802 * @param {number} r angle in radians 12803 * @return {number} angle in degrees 12804 */ 12805 Astro._rtd = function(r) { 12806 return (r * 180.0) / Math.PI; 12807 }; 12808 12809 /** 12810 * Return the cosine of an angle given in degrees. 12811 * @static 12812 * @protected 12813 * @param {number} d angle in degrees 12814 * @return {number} cosine of the angle. 12815 */ 12816 Astro._dcos = function(d) { 12817 return Math.cos(Astro._dtr(d)); 12818 }; 12819 12820 /** 12821 * Return the sine of an angle given in degrees. 12822 * @static 12823 * @protected 12824 * @param {number} d angle in degrees 12825 * @return {number} sine of the angle. 12826 */ 12827 Astro._dsin = function(d) { 12828 return Math.sin(Astro._dtr(d)); 12829 }; 12830 12831 /** 12832 * Return the tan of an angle given in degrees. 12833 * @static 12834 * @protected 12835 * @param {number} d angle in degrees 12836 * @return {number} tan of the angle. 12837 */ 12838 Astro._dtan = function(d) { 12839 return Math.tan(Astro._dtr(d)); 12840 }; 12841 12842 /** 12843 * Range reduce angle in degrees. 12844 * 12845 * @static 12846 * @param {number} a angle to reduce 12847 * @return {number} the reduced angle 12848 */ 12849 Astro._fixangle = function(a) { 12850 return a - 360.0 * (Math.floor(a / 360.0)); 12851 }; 12852 12853 /** 12854 * Range reduce angle in radians. 12855 * 12856 * @static 12857 * @protected 12858 * @param {number} a angle to reduce 12859 * @return {number} the reduced angle 12860 */ 12861 Astro._fixangr = function(a) { 12862 return a - (2 * Math.PI) * (Math.floor(a / (2 * Math.PI))); 12863 }; 12864 12865 /** 12866 * Determine the Julian Ephemeris Day of an equinox or solstice. The "which" 12867 * argument selects the item to be computed: 12868 * 12869 * <ul> 12870 * <li>0 March equinox 12871 * <li>1 June solstice 12872 * <li>2 September equinox 12873 * <li>3 December solstice 12874 * </ul> 12875 * 12876 * @static 12877 * @protected 12878 * @param {number} year Gregorian year to calculate for 12879 * @param {number} which Which equinox or solstice to calculate 12880 */ 12881 Astro._equinox = function(year, which) { 12882 var deltaL, i, j, JDE0, JDE, JDE0tab, S, T, W, Y; 12883 12884 /* Initialize terms for mean equinox and solstices. We 12885 have two sets: one for years prior to 1000 and a second 12886 for subsequent years. */ 12887 12888 if (year < 1000) { 12889 JDE0tab = ilib.data.astro._JDE0tab1000; 12890 Y = year / 1000; 12891 } else { 12892 JDE0tab = ilib.data.astro._JDE0tab2000; 12893 Y = (year - 2000) / 1000; 12894 } 12895 12896 JDE0 = JDE0tab[which][0] + (JDE0tab[which][1] * Y) 12897 + (JDE0tab[which][2] * Y * Y) + (JDE0tab[which][3] * Y * Y * Y) 12898 + (JDE0tab[which][4] * Y * Y * Y * Y); 12899 12900 //document.debug.log.value += "JDE0 = " + JDE0 + "\n"; 12901 12902 T = (JDE0 - 2451545.0) / 36525; 12903 //document.debug.log.value += "T = " + T + "\n"; 12904 W = (35999.373 * T) - 2.47; 12905 //document.debug.log.value += "W = " + W + "\n"; 12906 deltaL = 1 + (0.0334 * Astro._dcos(W)) + (0.0007 * Astro._dcos(2 * W)); 12907 //document.debug.log.value += "deltaL = " + deltaL + "\n"; 12908 12909 // Sum the periodic terms for time T 12910 12911 S = 0; 12912 j = 0; 12913 for (i = 0; i < 24; i++) { 12914 S += ilib.data.astro._EquinoxpTerms[j] 12915 * Astro._dcos(ilib.data.astro._EquinoxpTerms[j + 1] + (ilib.data.astro._EquinoxpTerms[j + 2] * T)); 12916 j += 3; 12917 } 12918 12919 //document.debug.log.value += "S = " + S + "\n"; 12920 //document.debug.log.value += "Corr = " + ((S * 0.00001) / deltaL) + "\n"; 12921 12922 JDE = JDE0 + ((S * 0.00001) / deltaL); 12923 12924 return JDE; 12925 }; 12926 12927 /* 12928 * The table of observed Delta T values at the beginning of 12929 * years from 1620 through 2014 as found in astro.json is taken from 12930 * http://www.staff.science.uu.nl/~gent0113/deltat/deltat.htm 12931 * and 12932 * ftp://maia.usno.navy.mil/ser7/deltat.data 12933 */ 12934 12935 /** 12936 * Determine the difference, in seconds, between dynamical time and universal time. 12937 * 12938 * @static 12939 * @protected 12940 * @param {number} year to calculate the difference for 12941 * @return {number} difference in seconds between dynamical time and universal time 12942 */ 12943 Astro._deltat = function (year) { 12944 var dt, f, i, t; 12945 12946 if ((year >= 1620) && (year <= 2014)) { 12947 i = Math.floor(year - 1620); 12948 f = (year - 1620) - i; /* Fractional part of year */ 12949 dt = ilib.data.astro._deltaTtab[i] + ((ilib.data.astro._deltaTtab[i + 1] - ilib.data.astro._deltaTtab[i]) * f); 12950 } else { 12951 t = (year - 2000) / 100; 12952 if (year < 948) { 12953 dt = 2177 + (497 * t) + (44.1 * t * t); 12954 } else { 12955 dt = 102 + (102 * t) + (25.3 * t * t); 12956 if ((year > 2000) && (year < 2100)) { 12957 dt += 0.37 * (year - 2100); 12958 } 12959 } 12960 } 12961 return dt; 12962 }; 12963 12964 /** 12965 * Calculate the obliquity of the ecliptic for a given 12966 * Julian date. This uses Laskar's tenth-degree 12967 * polynomial fit (J. Laskar, Astronomy and 12968 * Astrophysics, Vol. 157, page 68 [1986]) which is 12969 * accurate to within 0.01 arc second between AD 1000 12970 * and AD 3000, and within a few seconds of arc for 12971 * +/-10000 years around AD 2000. If we're outside the 12972 * range in which this fit is valid (deep time) we 12973 * simply return the J2000 value of the obliquity, which 12974 * happens to be almost precisely the mean. 12975 * 12976 * @static 12977 * @protected 12978 * @param {number} jd Julian Day to calculate the obliquity for 12979 * @return {number} the obliquity 12980 */ 12981 Astro._obliqeq = function (jd) { 12982 var eps, u, v, i; 12983 12984 v = u = (jd - 2451545.0) / 3652500.0; 12985 12986 eps = 23 + (26 / 60.0) + (21.448 / 3600.0); 12987 12988 if (Math.abs(u) < 1.0) { 12989 for (i = 0; i < 10; i++) { 12990 eps += (ilib.data.astro._oterms[i] / 3600.0) * v; 12991 v *= u; 12992 } 12993 } 12994 return eps; 12995 }; 12996 12997 /** 12998 * Return the position of the sun. We return 12999 * intermediate values because they are useful in a 13000 * variety of other contexts. 13001 * @static 13002 * @protected 13003 * @param {number} jd find the position of sun on this Julian Day 13004 * @return {Object} the position of the sun and many intermediate 13005 * values 13006 */ 13007 Astro._sunpos = function(jd) { 13008 var ret = {}, 13009 T, T2, T3, Omega, epsilon, epsilon0; 13010 13011 T = (jd - 2451545.0) / 36525.0; 13012 //document.debug.log.value += "Sunpos. T = " + T + "\n"; 13013 T2 = T * T; 13014 T3 = T * T2; 13015 ret.meanLongitude = Astro._fixangle(280.46646 + 36000.76983 * T + 0.0003032 * T2); 13016 //document.debug.log.value += "ret.meanLongitude = " + ret.meanLongitude + "\n"; 13017 ret.meanAnomaly = Astro._fixangle(357.52911 + (35999.05029 * T) - 0.0001537 * T2 - 0.00000048 * T3); 13018 //document.debug.log.value += "ret.meanAnomaly = " + ret.meanAnomaly + "\n"; 13019 ret.eccentricity = 0.016708634 - 0.000042037 * T - 0.0000001267 * T2; 13020 //document.debug.log.value += "e = " + e + "\n"; 13021 ret.equationOfCenter = ((1.914602 - 0.004817 * T - 0.000014 * T2) * Astro._dsin(ret.meanAnomaly)) 13022 + ((0.019993 - 0.000101 * T) * Astro._dsin(2 * ret.meanAnomaly)) 13023 + (0.000289 * Astro._dsin(3 * ret.meanAnomaly)); 13024 //document.debug.log.value += "ret.equationOfCenter = " + ret.equationOfCenter + "\n"; 13025 ret.sunLongitude = ret.meanLongitude + ret.equationOfCenter; 13026 //document.debug.log.value += "ret.sunLongitude = " + ret.sunLongitude + "\n"; 13027 //ret.sunAnomaly = ret.meanAnomaly + ret.equationOfCenter; 13028 //document.debug.log.value += "ret.sunAnomaly = " + ret.sunAnomaly + "\n"; 13029 // ret.sunRadius = (1.000001018 * (1 - (ret.eccentricity * ret.eccentricity))) / (1 + (ret.eccentricity * Astro._dcos(ret.sunAnomaly))); 13030 //document.debug.log.value += "ret.sunRadius = " + ret.sunRadius + "\n"; 13031 Omega = 125.04 - (1934.136 * T); 13032 //document.debug.log.value += "Omega = " + Omega + "\n"; 13033 ret.apparentLong = ret.sunLongitude + (-0.00569) + (-0.00478 * Astro._dsin(Omega)); 13034 //document.debug.log.value += "ret.apparentLong = " + ret.apparentLong + "\n"; 13035 epsilon0 = Astro._obliqeq(jd); 13036 //document.debug.log.value += "epsilon0 = " + epsilon0 + "\n"; 13037 epsilon = epsilon0 + (0.00256 * Astro._dcos(Omega)); 13038 //document.debug.log.value += "epsilon = " + epsilon + "\n"; 13039 //ret.rightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon0) * Astro._dsin(ret.sunLongitude), Astro._dcos(ret.sunLongitude)))); 13040 //document.debug.log.value += "ret.rightAscension = " + ret.rightAscension + "\n"; 13041 // ret.declination = Astro._rtd(Math.asin(Astro._dsin(epsilon0) * Astro._dsin(ret.sunLongitude))); 13042 ////document.debug.log.value += "ret.declination = " + ret.declination + "\n"; 13043 ret.inclination = Astro._fixangle(23.4392911 - 0.013004167 * T - 0.00000016389 * T2 + 0.0000005036 * T3); 13044 ret.apparentRightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon) * Astro._dsin(ret.apparentLong), Astro._dcos(ret.apparentLong)))); 13045 //document.debug.log.value += "ret.apparentRightAscension = " + ret.apparentRightAscension + "\n"; 13046 //ret.apparentDeclination = Astro._rtd(Math.asin(Astro._dsin(epsilon) * Astro._dsin(ret.apparentLong))); 13047 //document.debug.log.value += "ret.apparentDecliation = " + ret.apparentDecliation + "\n"; 13048 13049 // Angular quantities are expressed in decimal degrees 13050 return ret; 13051 }; 13052 13053 /** 13054 * Calculate the nutation in longitude, deltaPsi, and obliquity, 13055 * deltaEpsilon for a given Julian date jd. Results are returned as an object 13056 * giving deltaPsi and deltaEpsilon in degrees. 13057 * 13058 * @static 13059 * @protected 13060 * @param {number} jd calculate the nutation of this Julian Day 13061 * @return {Object} the deltaPsi and deltaEpsilon of the nutation 13062 */ 13063 Astro._nutation = function(jd) { 13064 var i, j, 13065 t = (jd - 2451545.0) / 36525.0, 13066 t2, t3, to10, 13067 ta = [], 13068 dp = 0, 13069 de = 0, 13070 ang, 13071 ret = {}; 13072 13073 t3 = t * (t2 = t * t); 13074 13075 /* 13076 * Calculate angles. The correspondence between the elements of our array 13077 * and the terms cited in Meeus are: 13078 * 13079 * ta[0] = D ta[0] = M ta[2] = M' ta[3] = F ta[4] = \Omega 13080 * 13081 */ 13082 13083 ta[0] = Astro._dtr(297.850363 + 445267.11148 * t - 0.0019142 * t2 + t3 / 189474.0); 13084 ta[1] = Astro._dtr(357.52772 + 35999.05034 * t - 0.0001603 * t2 - t3 / 300000.0); 13085 ta[2] = Astro._dtr(134.96298 + 477198.867398 * t + 0.0086972 * t2 + t3 / 56250.0); 13086 ta[3] = Astro._dtr(93.27191 + 483202.017538 * t - 0.0036825 * t2 + t3 / 327270); 13087 ta[4] = Astro._dtr(125.04452 - 1934.136261 * t + 0.0020708 * t2 + t3 / 450000.0); 13088 13089 /* 13090 * Range reduce the angles in case the sine and cosine functions don't do it 13091 * as accurately or quickly. 13092 */ 13093 13094 for (i = 0; i < 5; i++) { 13095 ta[i] = Astro._fixangr(ta[i]); 13096 } 13097 13098 to10 = t / 10.0; 13099 for (i = 0; i < 63; i++) { 13100 ang = 0; 13101 for (j = 0; j < 5; j++) { 13102 if (ilib.data.astro._nutArgMult[(i * 5) + j] != 0) { 13103 ang += ilib.data.astro._nutArgMult[(i * 5) + j] * ta[j]; 13104 } 13105 } 13106 dp += (ilib.data.astro._nutArgCoeff[(i * 4) + 0] + ilib.data.astro._nutArgCoeff[(i * 4) + 1] * to10) * Math.sin(ang); 13107 de += (ilib.data.astro._nutArgCoeff[(i * 4) + 2] + ilib.data.astro._nutArgCoeff[(i * 4) + 3] * to10) * Math.cos(ang); 13108 } 13109 13110 /* 13111 * Return the result, converting from ten thousandths of arc seconds to 13112 * radians in the process. 13113 */ 13114 13115 ret.deltaPsi = dp / (3600.0 * 10000.0); 13116 ret.deltaEpsilon = de / (3600.0 * 10000.0); 13117 13118 return ret; 13119 }; 13120 13121 /** 13122 * Returns the equation of time as a fraction of a day. 13123 * 13124 * @static 13125 * @protected 13126 * @param {number} jd the Julian Day of the day to calculate for 13127 * @return {number} the equation of time for the given day 13128 */ 13129 Astro._equationOfTime = function(jd) { 13130 var alpha, deltaPsi, E, epsilon, L0, tau, pos; 13131 13132 // 2451545.0 is the Julian day of J2000 epoch 13133 // 365250.0 is the number of days in a Julian millenium 13134 tau = (jd - 2451545.0) / 365250.0; 13135 //console.log("equationOfTime. tau = " + tau); 13136 L0 = 280.4664567 + (360007.6982779 * tau) + (0.03032028 * tau * tau) 13137 + ((tau * tau * tau) / 49931) 13138 + (-((tau * tau * tau * tau) / 15300)) 13139 + (-((tau * tau * tau * tau * tau) / 2000000)); 13140 //console.log("L0 = " + L0); 13141 L0 = Astro._fixangle(L0); 13142 //console.log("L0 = " + L0); 13143 pos = Astro._sunpos(jd); 13144 alpha = pos.apparentRightAscension; 13145 //console.log("alpha = " + alpha); 13146 var nut = Astro._nutation(jd); 13147 deltaPsi = nut.deltaPsi; 13148 //console.log("deltaPsi = " + deltaPsi); 13149 epsilon = Astro._obliqeq(jd) + nut.deltaEpsilon; 13150 //console.log("epsilon = " + epsilon); 13151 //console.log("L0 - 0.0057183 = " + (L0 - 0.0057183)); 13152 //console.log("L0 - 0.0057183 - alpha = " + (L0 - 0.0057183 - alpha)); 13153 //console.log("deltaPsi * cos(epsilon) = " + deltaPsi * Astro._dcos(epsilon)); 13154 13155 E = L0 - 0.0057183 - alpha + deltaPsi * Astro._dcos(epsilon); 13156 // if alpha and L0 are in different quadrants, then renormalize 13157 // so that the difference between them is in the right range 13158 if (E > 180) { 13159 E -= 360; 13160 } 13161 //console.log("E = " + E); 13162 // E = E - 20.0 * (Math.floor(E / 20.0)); 13163 E = E * 4; 13164 //console.log("Efixed = " + E); 13165 E = E / (24 * 60); 13166 //console.log("Eday = " + E); 13167 13168 return E; 13169 }; 13170 13171 /** 13172 * @private 13173 * @static 13174 */ 13175 Astro._poly = function(x, coefficients) { 13176 var result = coefficients[0]; 13177 var xpow = x; 13178 for (var i = 1; i < coefficients.length; i++) { 13179 result += coefficients[i] * xpow; 13180 xpow *= x; 13181 } 13182 return result; 13183 }; 13184 13185 /** 13186 * Calculate the UTC RD from the local RD given "zone" number of minutes 13187 * worth of offset. 13188 * 13189 * @static 13190 * @protected 13191 * @param {number} local RD of the locale time, given in any calendar 13192 * @param {number} zone number of minutes of offset from UTC for the time zone 13193 * @return {number} the UTC equivalent of the local RD 13194 */ 13195 Astro._universalFromLocal = function(local, zone) { 13196 return local - zone / 1440; 13197 }; 13198 13199 /** 13200 * Calculate the local RD from the UTC RD given "zone" number of minutes 13201 * worth of offset. 13202 * 13203 * @static 13204 * @protected 13205 * @param {number} local RD of the locale time, given in any calendar 13206 * @param {number} zone number of minutes of offset from UTC for the time zone 13207 * @return {number} the UTC equivalent of the local RD 13208 */ 13209 Astro._localFromUniversal = function(local, zone) { 13210 return local + zone / 1440; 13211 }; 13212 13213 /** 13214 * @private 13215 * @static 13216 * @param {number} c julian centuries of the date to calculate 13217 * @return {number} the aberration 13218 */ 13219 Astro._aberration = function(c) { 13220 return 9.74e-05 * Astro._dcos(177.63 + 35999.01847999999 * c) - 0.005575; 13221 }; 13222 13223 /** 13224 * @private 13225 * 13226 ilib.data.astro._nutCoeffA = [124.90, -1934.134, 0.002063]; 13227 ilib.data.astro._nutCoeffB q= [201.11, 72001.5377, 0.00057]; 13228 */ 13229 13230 /** 13231 * @private 13232 * @static 13233 * @param {number} c julian centuries of the date to calculate 13234 * @return {number} the nutation for the given julian century in radians 13235 */ 13236 Astro._nutation2 = function(c) { 13237 var a = Astro._poly(c, ilib.data.astro._nutCoeffA); 13238 var b = Astro._poly(c, ilib.data.astro._nutCoeffB); 13239 // return -0.0000834 * Astro._dsin(a) - 0.0000064 * Astro._dsin(b); 13240 return -0.004778 * Astro._dsin(a) - 0.0003667 * Astro._dsin(b); 13241 }; 13242 13243 /** 13244 * @static 13245 * @private 13246 */ 13247 Astro._ephemerisCorrection = function(jd) { 13248 var year = GregorianDate._calcYear(jd - 1721424.5); 13249 13250 if (1988 <= year && year <= 2019) { 13251 return (year - 1933) / 86400; 13252 } 13253 13254 if (1800 <= year && year <= 1987) { 13255 var jul1 = new GregRataDie({ 13256 year: year, 13257 month: 7, 13258 day: 1, 13259 hour: 0, 13260 minute: 0, 13261 second: 0 13262 }); 13263 // 693596 is the rd of Jan 1, 1900 13264 var theta = (jul1.getRataDie() - 693596) / 36525; 13265 return Astro._poly(theta, (1900 <= year) ? ilib.data.astro._coeff19th : ilib.data.astro._coeff18th); 13266 } 13267 13268 if (1620 <= year && year <= 1799) { 13269 year -= 1600; 13270 return (196.58333 - 4.0675 * year + 0.0219167 * year * year) / 86400; 13271 } 13272 13273 // 660724 is the rd of Jan 1, 1810 13274 var jan1 = new GregRataDie({ 13275 year: year, 13276 month: 1, 13277 day: 1, 13278 hour: 0, 13279 minute: 0, 13280 second: 0 13281 }); 13282 // var x = 0.5 + (jan1.getRataDie() - 660724); 13283 var x = 0.5 + (jan1.getRataDie() - 660724); 13284 13285 return ((x * x / 41048480) - 15) / 86400; 13286 }; 13287 13288 /** 13289 * @static 13290 * @private 13291 */ 13292 Astro._ephemerisFromUniversal = function(jd) { 13293 return jd + Astro._ephemerisCorrection(jd); 13294 }; 13295 13296 /** 13297 * @static 13298 * @private 13299 */ 13300 Astro._universalFromEphemeris = function(jd) { 13301 return jd - Astro._ephemerisCorrection(jd); 13302 }; 13303 13304 /** 13305 * @static 13306 * @private 13307 */ 13308 Astro._julianCenturies = function(jd) { 13309 // 2451545.0 is the Julian day of J2000 epoch 13310 // 730119.5 is the Gregorian RD of J2000 epoch 13311 // 36525.0 is the number of days in a Julian century 13312 return (Astro._ephemerisFromUniversal(jd) - 2451545.0) / 36525.0; 13313 }; 13314 13315 /** 13316 * Calculate the solar longitude 13317 * 13318 * @static 13319 * @protected 13320 * @param {number} jd julian day of the date to calculate the longitude for 13321 * @return {number} the solar longitude in degrees 13322 */ 13323 Astro._solarLongitude = function(jd) { 13324 var c = Astro._julianCenturies(jd), 13325 longitude = 0, 13326 len = ilib.data.astro._solarLongCoeff.length, 13327 row; 13328 13329 for (var i = 0; i < len; i++) { 13330 longitude += ilib.data.astro._solarLongCoeff[i] * 13331 Astro._dsin(ilib.data.astro._solarLongAddends[i] + ilib.data.astro._solarLongMultipliers[i] * c); 13332 } 13333 longitude *= 5.729577951308232e-06; 13334 longitude += 282.77718340000001 + 36000.769537439999 * c; 13335 longitude += Astro._aberration(c) + Astro._nutation2(c); 13336 return Astro._fixangle(longitude); 13337 }; 13338 13339 /** 13340 * @static 13341 * @protected 13342 * @param {number} jd 13343 * @return {number} 13344 */ 13345 Astro._lunarLongitude = function (jd) { 13346 var c = Astro._julianCenturies(jd), 13347 meanMoon = Astro._fixangle(Astro._poly(c, ilib.data.astro._meanMoonCoeff)), 13348 elongation = Astro._fixangle(Astro._poly(c, ilib.data.astro._elongationCoeff)), 13349 solarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._solarAnomalyCoeff)), 13350 lunarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._lunarAnomalyCoeff)), 13351 moonNode = Astro._fixangle(Astro._poly(c, ilib.data.astro._moonFromNodeCoeff)), 13352 e = Astro._poly(c, ilib.data.astro._eCoeff); 13353 13354 var sum = 0; 13355 for (var i = 0; i < ilib.data.astro._lunarElongationLongCoeff.length; i++) { 13356 var x = ilib.data.astro._solarAnomalyLongCoeff[i]; 13357 13358 sum += ilib.data.astro._sineCoeff[i] * Math.pow(e, Math.abs(x)) * 13359 Astro._dsin(ilib.data.astro._lunarElongationLongCoeff[i] * elongation + x * solarAnomaly + 13360 ilib.data.astro._lunarAnomalyLongCoeff[i] * lunarAnomaly + 13361 ilib.data.astro._moonFromNodeLongCoeff[i] * moonNode); 13362 } 13363 var longitude = sum / 1000000; 13364 var venus = 3958.0 / 1000000 * Astro._dsin(119.75 + c * 131.84899999999999); 13365 var jupiter = 318.0 / 1000000 * Astro._dsin(53.090000000000003 + c * 479264.28999999998); 13366 var flatEarth = 1962.0 / 1000000 * Astro._dsin(meanMoon - moonNode); 13367 13368 return Astro._fixangle(meanMoon + longitude + venus + jupiter + flatEarth + Astro._nutation2(c)); 13369 }; 13370 13371 /** 13372 * @static 13373 * @protected 13374 * @param {number} n 13375 * @return {number} julian day of the n'th new moon 13376 */ 13377 Astro._newMoonTime = function(n) { 13378 var k = n - 24724; 13379 var c = k / 1236.8499999999999; 13380 var approx = Astro._poly(c, ilib.data.astro._nmApproxCoeff); 13381 var capE = Astro._poly(c, ilib.data.astro._nmCapECoeff); 13382 var solarAnomaly = Astro._poly(c, ilib.data.astro._nmSolarAnomalyCoeff); 13383 var lunarAnomaly = Astro._poly(c, ilib.data.astro._nmLunarAnomalyCoeff); 13384 var moonArgument = Astro._poly(c, ilib.data.astro._nmMoonArgumentCoeff); 13385 var capOmega = Astro._poly(c, ilib.data.astro._nmCapOmegaCoeff); 13386 var correction = -0.00017 * Astro._dsin(capOmega); 13387 for (var i = 0; i < ilib.data.astro._nmSineCoeff.length; i++) { 13388 correction = correction + ilib.data.astro._nmSineCoeff[i] * Math.pow(capE, ilib.data.astro._nmEFactor[i]) * 13389 Astro._dsin(ilib.data.astro._nmSolarCoeff[i] * solarAnomaly + 13390 ilib.data.astro._nmLunarCoeff[i] * lunarAnomaly + 13391 ilib.data.astro._nmMoonCoeff[i] * moonArgument); 13392 } 13393 var additional = 0; 13394 for (var i = 0; i < ilib.data.astro._nmAddConst.length; i++) { 13395 additional = additional + ilib.data.astro._nmAddFactor[i] * 13396 Astro._dsin(ilib.data.astro._nmAddConst[i] + ilib.data.astro._nmAddCoeff[i] * k); 13397 } 13398 var extra = 0.000325 * Astro._dsin(Astro._poly(c, ilib.data.astro._nmExtra)); 13399 return Astro._universalFromEphemeris(approx + correction + extra + additional + RataDie.gregorianEpoch); 13400 }; 13401 13402 /** 13403 * @static 13404 * @protected 13405 * @param {number} jd 13406 * @return {number} 13407 */ 13408 Astro._lunarSolarAngle = function(jd) { 13409 var lunar = Astro._lunarLongitude(jd); 13410 var solar = Astro._solarLongitude(jd) 13411 return Astro._fixangle(lunar - solar); 13412 }; 13413 13414 /** 13415 * @static 13416 * @protected 13417 * @param {number} jd 13418 * @return {number} 13419 */ 13420 Astro._newMoonBefore = function (jd) { 13421 var phase = Astro._lunarSolarAngle(jd); 13422 // 11.450086114414322 is the julian day of the 0th full moon 13423 // 29.530588853000001 is the average length of a month 13424 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360) - 1; 13425 var current, last; 13426 current = last = Astro._newMoonTime(guess); 13427 while (current < jd) { 13428 guess++; 13429 last = current; 13430 current = Astro._newMoonTime(guess); 13431 } 13432 return last; 13433 }; 13434 13435 /** 13436 * @static 13437 * @protected 13438 * @param {number} jd 13439 * @return {number} 13440 */ 13441 Astro._newMoonAtOrAfter = function (jd) { 13442 var phase = Astro._lunarSolarAngle(jd); 13443 // 11.450086114414322 is the julian day of the 0th full moon 13444 // 29.530588853000001 is the average length of a month 13445 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360); 13446 var current; 13447 while ((current = Astro._newMoonTime(guess)) < jd) { 13448 guess++; 13449 } 13450 return current; 13451 }; 13452 13453 /** 13454 * @static 13455 * @protected 13456 * @param {number} jd JD to calculate from 13457 * @param {number} longitude longitude to seek 13458 * @returns {number} the JD of the next time that the solar longitude 13459 * is a multiple of the given longitude 13460 */ 13461 Astro._nextSolarLongitude = function(jd, longitude) { 13462 var rate = 365.242189 / 360.0; 13463 var tau = jd + rate * Astro._fixangle(longitude - Astro._solarLongitude(jd)); 13464 var start = Math.max(jd, tau - 5.0); 13465 var end = tau + 5.0; 13466 13467 return SearchUtils.bisectionSearch(0, start, end, 1e-6, function (l) { 13468 return 180 - Astro._fixangle(Astro._solarLongitude(l) - longitude); 13469 }); 13470 }; 13471 13472 /** 13473 * Floor the julian day to midnight of the current julian day. 13474 * 13475 * @static 13476 * @protected 13477 * @param {number} jd the julian to round 13478 * @return {number} the jd floored to the midnight of the julian day 13479 */ 13480 Astro._floorToJD = function(jd) { 13481 return Math.floor(jd - 0.5) + 0.5; 13482 }; 13483 13484 /** 13485 * Floor the julian day to midnight of the current julian day. 13486 * 13487 * @static 13488 * @protected 13489 * @param {number} jd the julian to round 13490 * @return {number} the jd floored to the midnight of the julian day 13491 */ 13492 Astro._ceilToJD = function(jd) { 13493 return Math.ceil(jd + 0.5) - 0.5; 13494 }; 13495 13496 13497 13498 /*< PersRataDie.js */ 13499 /* 13500 * persratadie.js - Represent a rata die date in the Persian calendar 13501 * 13502 * Copyright © 2014-2015, JEDLSoft 13503 * 13504 * Licensed under the Apache License, Version 2.0 (the "License"); 13505 * you may not use this file except in compliance with the License. 13506 * You may obtain a copy of the License at 13507 * 13508 * http://www.apache.org/licenses/LICENSE-2.0 13509 * 13510 * Unless required by applicable law or agreed to in writing, software 13511 * distributed under the License is distributed on an "AS IS" BASIS, 13512 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13513 * 13514 * See the License for the specific language governing permissions and 13515 * limitations under the License. 13516 */ 13517 13518 /* !depends 13519 ilib.js 13520 MathUtils.js 13521 RataDie.js 13522 Astro.js 13523 GregorianDate.js 13524 */ 13525 13526 13527 13528 13529 /** 13530 * @class 13531 * Construct a new Persian RD date number object. The constructor parameters can 13532 * contain any of the following properties: 13533 * 13534 * <ul> 13535 * <li><i>unixtime<i> - sets the time of this instance according to the given 13536 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13537 * 13538 * <li><i>julianday</i> - sets the time of this instance according to the given 13539 * Julian Day instance or the Julian Day given as a float 13540 * 13541 * <li><i>year</i> - any integer, including 0 13542 * 13543 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13544 * 13545 * <li><i>day</i> - 1 to 31 13546 * 13547 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13548 * is always done with an unambiguous 24 hour representation 13549 * 13550 * <li><i>minute</i> - 0 to 59 13551 * 13552 * <li><i>second</i> - 0 to 59 13553 * 13554 * <li><i>millisecond</i> - 0 to 999 13555 * 13556 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 13557 * </ul> 13558 * 13559 * If the constructor is called with another Persian date instance instead of 13560 * a parameter block, the other instance acts as a parameter block and its 13561 * settings are copied into the current instance.<p> 13562 * 13563 * If the constructor is called with no arguments at all or if none of the 13564 * properties listed above are present, then the RD is calculate based on 13565 * the current date at the time of instantiation. <p> 13566 * 13567 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 13568 * specified in the params, it is assumed that they have the smallest possible 13569 * value in the range for the property (zero or one).<p> 13570 * 13571 * 13572 * @private 13573 * @constructor 13574 * @extends RataDie 13575 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 13576 */ 13577 var PersRataDie = function(params) { 13578 this.rd = NaN; 13579 Astro.initAstro( 13580 params && typeof(params.sync) === 'boolean' ? params.sync : true, 13581 params && params.loadParams, 13582 ilib.bind(this, function (x) { 13583 RataDie.call(this, params); 13584 if (params && typeof(params.callback) === 'function') { 13585 params.callback(this); 13586 } 13587 }) 13588 ); 13589 }; 13590 13591 PersRataDie.prototype = new RataDie(); 13592 PersRataDie.prototype.parent = RataDie; 13593 PersRataDie.prototype.constructor = PersRataDie; 13594 13595 /** 13596 * The difference between a zero Julian day and the first Persian date 13597 * @private 13598 * @type number 13599 */ 13600 PersRataDie.prototype.epoch = 1948319.5; 13601 13602 /** 13603 * @protected 13604 */ 13605 PersRataDie.prototype._tehranEquinox = function(year) { 13606 var equJED, equJD, equAPP, equTehran, dtTehran, eot; 13607 13608 // March equinox in dynamical time 13609 equJED = Astro._equinox(year, 0); 13610 13611 // Correct for delta T to obtain Universal time 13612 equJD = equJED - (Astro._deltat(year) / (24 * 60 * 60)); 13613 13614 // Apply the equation of time to yield the apparent time at Greenwich 13615 eot = Astro._equationOfTime(equJED) * 360; 13616 eot = (eot - 20 * Math.floor(eot/20)) / 360; 13617 equAPP = equJD + eot; 13618 13619 /* 13620 * Finally, we must correct for the constant difference between 13621 * the Greenwich meridian and the time zone standard for Iran 13622 * Standard time, 52 degrees 30 minutes to the East. 13623 */ 13624 13625 dtTehran = 52.5 / 360; 13626 equTehran = equAPP + dtTehran; 13627 13628 return equTehran; 13629 }; 13630 13631 /** 13632 * Calculate the year based on the given Julian day. 13633 * @protected 13634 * @param {number} jd the Julian day to get the year for 13635 * @return {{year:number,equinox:number}} the year and the last equinox 13636 */ 13637 PersRataDie.prototype._getYear = function(jd) { 13638 var gd = new GregorianDate({julianday: jd}); 13639 var guess = gd.getYears() - 2, 13640 nexteq, 13641 ret = {}; 13642 13643 //ret.equinox = Math.floor(this._tehranEquinox(guess)); 13644 ret.equinox = this._tehranEquinox(guess); 13645 while (ret.equinox > jd) { 13646 guess--; 13647 // ret.equinox = Math.floor(this._tehranEquinox(guess)); 13648 ret.equinox = this._tehranEquinox(guess); 13649 } 13650 nexteq = ret.equinox - 1; 13651 // if the equinox falls after noon, then the day after that is the start of the 13652 // next year, so truncate the JD to get the noon of the day before the day with 13653 //the equinox on it, then add 0.5 to get the midnight of that day 13654 while (!(Math.floor(ret.equinox) + 0.5 <= jd && jd < Math.floor(nexteq) + 0.5)) { 13655 ret.equinox = nexteq; 13656 guess++; 13657 // nexteq = Math.floor(this._tehranEquinox(guess)); 13658 nexteq = this._tehranEquinox(guess); 13659 } 13660 13661 // Mean solar tropical year is 365.24219878 days 13662 ret.year = Math.round((ret.equinox - this.epoch - 1) / 365.24219878) + 1; 13663 13664 return ret; 13665 }; 13666 13667 /** 13668 * Calculate the Rata Die (fixed day) number of the given date from the 13669 * date components. 13670 * 13671 * @protected 13672 * @param {Object} date the date components to calculate the RD from 13673 */ 13674 PersRataDie.prototype._setDateComponents = function(date) { 13675 var adr, guess, jd; 13676 13677 // Mean solar tropical year is 365.24219878 days 13678 guess = this.epoch + 1 + 365.24219878 * (date.year - 2); 13679 adr = {year: date.year - 1, equinox: 0}; 13680 13681 while (adr.year < date.year) { 13682 adr = this._getYear(guess); 13683 guess = adr.equinox + (365.24219878 + 2); 13684 } 13685 13686 jd = Math.floor(adr.equinox) + 13687 ((date.month <= 7) ? 13688 ((date.month - 1) * 31) : 13689 (((date.month - 1) * 30) + 6) 13690 ) + 13691 (date.day - 1 + 0.5); // add 0.5 so that we convert JDs, which start at noon to RDs which start at midnight 13692 13693 jd += (date.hour * 3600000 + 13694 date.minute * 60000 + 13695 date.second * 1000 + 13696 date.millisecond) / 13697 86400000; 13698 13699 this.rd = jd - this.epoch; 13700 }; 13701 13702 /** 13703 * Return the rd number of the particular day of the week on or before the 13704 * given rd. eg. The Sunday on or before the given rd. 13705 * @private 13706 * @param {number} rd the rata die date of the reference date 13707 * @param {number} dayOfWeek the day of the week that is being sought relative 13708 * to the current date 13709 * @return {number} the rd of the day of the week 13710 */ 13711 PersRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 13712 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 13713 }; 13714 13715 13716 /*< PersianCal.js */ 13717 /* 13718 * persianastro.js - Represent a Persian astronomical (Hijjri) calendar object. 13719 * 13720 * Copyright © 2014-2015, JEDLSoft 13721 * 13722 * Licensed under the Apache License, Version 2.0 (the "License"); 13723 * you may not use this file except in compliance with the License. 13724 * You may obtain a copy of the License at 13725 * 13726 * http://www.apache.org/licenses/LICENSE-2.0 13727 * 13728 * Unless required by applicable law or agreed to in writing, software 13729 * distributed under the License is distributed on an "AS IS" BASIS, 13730 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13731 * 13732 * See the License for the specific language governing permissions and 13733 * limitations under the License. 13734 */ 13735 13736 13737 /* !depends 13738 Calendar.js 13739 PersRataDie.js 13740 ilib.js 13741 MathUtils.js 13742 */ 13743 13744 13745 13746 13747 /** 13748 * @class 13749 * Construct a new Persian astronomical (Hijjri) calendar object. This class encodes 13750 * information about a Persian calendar. This class differs from the 13751 * Persian calendar in that the leap years are calculated based on the 13752 * astronomical observations of the sun in Teheran, instead of calculating 13753 * the leap years based on a regular cyclical rhythm algorithm.<p> 13754 * 13755 * 13756 * @constructor 13757 * @extends Calendar 13758 */ 13759 var PersianCal = function() { 13760 this.type = "persian"; 13761 }; 13762 13763 /** 13764 * @private 13765 * @const 13766 * @type Array.<number> 13767 * the lengths of each month 13768 */ 13769 PersianCal.monthLengths = [ 13770 31, // Farvardin 13771 31, // Ordibehesht 13772 31, // Khordad 13773 31, // Tir 13774 31, // Mordad 13775 31, // Shahrivar 13776 30, // Mehr 13777 30, // Aban 13778 30, // Azar 13779 30, // Dey 13780 30, // Bahman 13781 29 // Esfand 13782 ]; 13783 13784 /** 13785 * Return the number of months in the given year. The number of months in a year varies 13786 * for some luni-solar calendars because in some years, an extra month is needed to extend the 13787 * days in a year to an entire solar year. The month is represented as a 1-based number 13788 * where 1=first month, 2=second month, etc. 13789 * 13790 * @param {number} year a year for which the number of months is sought 13791 * @return {number} The number of months in the given year 13792 */ 13793 PersianCal.prototype.getNumMonths = function(year) { 13794 return 12; 13795 }; 13796 13797 /** 13798 * Return the number of days in a particular month in a particular year. This function 13799 * can return a different number for a month depending on the year because of things 13800 * like leap years. 13801 * 13802 * @param {number} month the month for which the length is sought 13803 * @param {number} year the year within which that month can be found 13804 * @return {number} the number of days within the given month in the given year 13805 */ 13806 PersianCal.prototype.getMonLength = function(month, year) { 13807 if (month !== 12 || !this.isLeapYear(year)) { 13808 return PersianCal.monthLengths[month-1]; 13809 } else { 13810 // Month 12, Esfand, has 30 days instead of 29 in leap years 13811 return 30; 13812 } 13813 }; 13814 13815 /** 13816 * Return true if the given year is a leap year in the Persian astronomical calendar. 13817 * @param {number} year the year for which the leap year information is being sought 13818 * @return {boolean} true if the given year is a leap year 13819 */ 13820 PersianCal.prototype.isLeapYear = function(year) { 13821 var rdNextYear = new PersRataDie({ 13822 cal: this, 13823 year: year + 1, 13824 month: 1, 13825 day: 1, 13826 hour: 0, 13827 minute: 0, 13828 second: 0, 13829 millisecond: 0 13830 }); 13831 var rdThisYear = new PersRataDie({ 13832 cal: this, 13833 year: year, 13834 month: 1, 13835 day: 1, 13836 hour: 0, 13837 minute: 0, 13838 second: 0, 13839 millisecond: 0 13840 }); 13841 return (rdNextYear.getRataDie() - rdThisYear.getRataDie()) > 365; 13842 }; 13843 13844 /** 13845 * Return the type of this calendar. 13846 * 13847 * @return {string} the name of the type of this calendar 13848 */ 13849 PersianCal.prototype.getType = function() { 13850 return this.type; 13851 }; 13852 13853 /** 13854 * Return a date instance for this calendar type using the given 13855 * options. 13856 * @param {Object} options options controlling the construction of 13857 * the date instance 13858 * @return {IDate} a date appropriate for this calendar type 13859 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 13860 */ 13861 PersianCal.prototype.newDateInstance = function (options) { 13862 return new PersianDate(options); 13863 }; 13864 13865 /* register this calendar for the factory method */ 13866 Calendar._constructors["persian"] = PersianCal; 13867 13868 13869 /*< PersianDate.js */ 13870 /* 13871 * PersianDate.js - Represent a date in the Persian astronomical (Hijjri) calendar 13872 * 13873 * Copyright © 2014-2015, JEDLSoft 13874 * 13875 * Licensed under the Apache License, Version 2.0 (the "License"); 13876 * you may not use this file except in compliance with the License. 13877 * You may obtain a copy of the License at 13878 * 13879 * http://www.apache.org/licenses/LICENSE-2.0 13880 * 13881 * Unless required by applicable law or agreed to in writing, software 13882 * distributed under the License is distributed on an "AS IS" BASIS, 13883 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13884 * 13885 * See the License for the specific language governing permissions and 13886 * limitations under the License. 13887 */ 13888 13889 /* !depends 13890 ilib.js 13891 Locale.js 13892 TimeZone.js 13893 IDate.js 13894 PersRataDie.js 13895 PersianCal.js 13896 SearchUtils.js 13897 MathUtils.js 13898 LocaleInfo.js 13899 Astro.js 13900 */ 13901 13902 // !data astro 13903 13904 13905 13906 13907 /** 13908 * @class 13909 * 13910 * Construct a new Persian astronomical date object. The constructor parameters can 13911 * contain any of the following properties: 13912 * 13913 * <ul> 13914 * <li><i>unixtime<i> - sets the time of this instance according to the given 13915 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13916 * 13917 * <li><i>julianday</i> - sets the time of this instance according to the given 13918 * Julian Day instance or the Julian Day given as a float 13919 * 13920 * <li><i>year</i> - any integer, including 0 13921 * 13922 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13923 * 13924 * <li><i>day</i> - 1 to 31 13925 * 13926 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13927 * is always done with an unambiguous 24 hour representation 13928 * 13929 * <li><i>minute</i> - 0 to 59 13930 * 13931 * <li><i>second</i> - 0 to 59 13932 * 13933 * <li><i>millisecond</i> - 0 to 999 13934 * 13935 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 13936 * of this persian date. The date/time is kept in the local time. The time zone 13937 * is used later if this date is formatted according to a different time zone and 13938 * the difference has to be calculated, or when the date format has a time zone 13939 * component in it. 13940 * 13941 * <li><i>locale</i> - locale for this persian date. If the time zone is not 13942 * given, it can be inferred from this locale. For locales that span multiple 13943 * time zones, the one with the largest population is chosen as the one that 13944 * represents the locale. 13945 * 13946 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 13947 * </ul> 13948 * 13949 * If the constructor is called with another Persian date instance instead of 13950 * a parameter block, the other instance acts as a parameter block and its 13951 * settings are copied into the current instance.<p> 13952 * 13953 * If the constructor is called with no arguments at all or if none of the 13954 * properties listed above 13955 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 13956 * components are 13957 * filled in with the current date at the time of instantiation. Note that if 13958 * you do not give the time zone when defaulting to the current time and the 13959 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 13960 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 13961 * Mean Time").<p> 13962 * 13963 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 13964 * specified in the params, it is assumed that they have the smallest possible 13965 * value in the range for the property (zero or one).<p> 13966 * 13967 * 13968 * @constructor 13969 * @extends IDate 13970 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 13971 */ 13972 var PersianDate = function(params) { 13973 this.cal = new PersianCal(); 13974 this.timezone = "local"; 13975 13976 if (params) { 13977 if (params.locale) { 13978 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 13979 var li = new LocaleInfo(this.locale); 13980 this.timezone = li.getTimeZone(); 13981 } 13982 if (params.timezone) { 13983 this.timezone = params.timezone; 13984 } 13985 } 13986 13987 Astro.initAstro( 13988 params && typeof(params.sync) === 'boolean' ? params.sync : true, 13989 params && params.loadParams, 13990 ilib.bind(this, function (x) { 13991 if (params && (params.year || params.month || params.day || params.hour || 13992 params.minute || params.second || params.millisecond)) { 13993 /** 13994 * Year in the Persian calendar. 13995 * @type number 13996 */ 13997 this.year = parseInt(params.year, 10) || 0; 13998 13999 /** 14000 * The month number, ranging from 1 to 12 14001 * @type number 14002 */ 14003 this.month = parseInt(params.month, 10) || 1; 14004 14005 /** 14006 * The day of the month. This ranges from 1 to 31. 14007 * @type number 14008 */ 14009 this.day = parseInt(params.day, 10) || 1; 14010 14011 /** 14012 * The hour of the day. This can be a number from 0 to 23, as times are 14013 * stored unambiguously in the 24-hour clock. 14014 * @type number 14015 */ 14016 this.hour = parseInt(params.hour, 10) || 0; 14017 14018 /** 14019 * The minute of the hours. Ranges from 0 to 59. 14020 * @type number 14021 */ 14022 this.minute = parseInt(params.minute, 10) || 0; 14023 14024 /** 14025 * The second of the minute. Ranges from 0 to 59. 14026 * @type number 14027 */ 14028 this.second = parseInt(params.second, 10) || 0; 14029 14030 /** 14031 * The millisecond of the second. Ranges from 0 to 999. 14032 * @type number 14033 */ 14034 this.millisecond = parseInt(params.millisecond, 10) || 0; 14035 14036 /** 14037 * The day of the year. Ranges from 1 to 366. 14038 * @type number 14039 */ 14040 this.dayOfYear = parseInt(params.dayOfYear, 10); 14041 14042 if (typeof(params.dst) === 'boolean') { 14043 this.dst = params.dst; 14044 } 14045 14046 this.rd = this.newRd(this); 14047 14048 // add the time zone offset to the rd to convert to UTC 14049 if (!this.tz) { 14050 this.tz = new TimeZone({id: this.timezone}); 14051 } 14052 // getOffsetMillis requires that this.year, this.rd, and this.dst 14053 // are set in order to figure out which time zone rules apply and 14054 // what the offset is at that point in the year 14055 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 14056 if (this.offset !== 0) { 14057 this.rd = this.newRd({ 14058 rd: this.rd.getRataDie() - this.offset 14059 }); 14060 } 14061 } 14062 14063 if (!this.rd) { 14064 this.rd = this.newRd(params); 14065 this._calcDateComponents(); 14066 } 14067 14068 if (params && typeof(params.onLoad) === 'function') { 14069 params.onLoad(this); 14070 } 14071 }) 14072 ); 14073 }; 14074 14075 PersianDate.prototype = new IDate({noinstance: true}); 14076 PersianDate.prototype.parent = IDate; 14077 PersianDate.prototype.constructor = PersianDate; 14078 14079 /** 14080 * @private 14081 * @const 14082 * @type Array.<number> 14083 * the cumulative lengths of each month, for a non-leap year 14084 */ 14085 PersianDate.cumMonthLengths = [ 14086 0, // Farvardin 14087 31, // Ordibehesht 14088 62, // Khordad 14089 93, // Tir 14090 124, // Mordad 14091 155, // Shahrivar 14092 186, // Mehr 14093 216, // Aban 14094 246, // Azar 14095 276, // Dey 14096 306, // Bahman 14097 336, // Esfand 14098 366 14099 ]; 14100 14101 /** 14102 * Return a new RD for this date type using the given params. 14103 * @protected 14104 * @param {Object=} params the parameters used to create this rata die instance 14105 * @returns {RataDie} the new RD instance for the given params 14106 */ 14107 PersianDate.prototype.newRd = function (params) { 14108 return new PersRataDie(params); 14109 }; 14110 14111 /** 14112 * Return the year for the given RD 14113 * @protected 14114 * @param {number} rd RD to calculate from 14115 * @returns {number} the year for the RD 14116 */ 14117 PersianDate.prototype._calcYear = function(rd) { 14118 var julianday = rd + this.rd.epoch; 14119 return this.rd._getYear(julianday).year; 14120 }; 14121 14122 /** 14123 * @private 14124 * Calculate date components for the given RD date. 14125 */ 14126 PersianDate.prototype._calcDateComponents = function () { 14127 var remainder, 14128 rd = this.rd.getRataDie(); 14129 14130 this.year = this._calcYear(rd); 14131 14132 if (typeof(this.offset) === "undefined") { 14133 // now offset the RD by the time zone, then recalculate in case we were 14134 // near the year boundary 14135 if (!this.tz) { 14136 this.tz = new TimeZone({id: this.timezone}); 14137 } 14138 this.offset = this.tz.getOffsetMillis(this) / 86400000; 14139 } 14140 14141 if (this.offset !== 0) { 14142 rd += this.offset; 14143 this.year = this._calcYear(rd); 14144 } 14145 14146 //console.log("PersDate.calcComponent: calculating for rd " + rd); 14147 //console.log("PersDate.calcComponent: year is " + ret.year); 14148 var yearStart = this.newRd({ 14149 year: this.year, 14150 month: 1, 14151 day: 1, 14152 hour: 0, 14153 minute: 0, 14154 second: 0, 14155 millisecond: 0 14156 }); 14157 remainder = rd - yearStart.getRataDie() + 1; 14158 14159 this.dayOfYear = remainder; 14160 14161 //console.log("PersDate.calcComponent: remainder is " + remainder); 14162 14163 this.month = SearchUtils.bsearch(Math.floor(remainder), PersianDate.cumMonthLengths); 14164 remainder -= PersianDate.cumMonthLengths[this.month-1]; 14165 14166 //console.log("PersDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 14167 14168 this.day = Math.floor(remainder); 14169 remainder -= this.day; 14170 14171 //console.log("PersDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 14172 14173 // now convert to milliseconds for the rest of the calculation 14174 remainder = Math.round(remainder * 86400000); 14175 14176 this.hour = Math.floor(remainder/3600000); 14177 remainder -= this.hour * 3600000; 14178 14179 this.minute = Math.floor(remainder/60000); 14180 remainder -= this.minute * 60000; 14181 14182 this.second = Math.floor(remainder/1000); 14183 remainder -= this.second * 1000; 14184 14185 this.millisecond = remainder; 14186 }; 14187 14188 /** 14189 * Return the day of the week of this date. The day of the week is encoded 14190 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 14191 * 14192 * @return {number} the day of the week 14193 */ 14194 PersianDate.prototype.getDayOfWeek = function() { 14195 var rd = Math.floor(this.getRataDie()); 14196 return MathUtils.mod(rd-3, 7); 14197 }; 14198 14199 /** 14200 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 14201 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 14202 * December 31st is 365 in regular years, or 366 in leap years. 14203 * @return {number} the ordinal day of the year 14204 */ 14205 PersianDate.prototype.getDayOfYear = function() { 14206 return PersianDate.cumMonthLengths[this.month-1] + this.day; 14207 }; 14208 14209 /** 14210 * Return the era for this date as a number. The value for the era for Persian 14211 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 14212 * persico or AP). 14213 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 14214 * there is a year 0, so any years that are negative or zero are BP. 14215 * @return {number} 1 if this date is in the common era, -1 if it is before the 14216 * common era 14217 */ 14218 PersianDate.prototype.getEra = function() { 14219 return (this.year < 1) ? -1 : 1; 14220 }; 14221 14222 /** 14223 * Return the name of the calendar that governs this date. 14224 * 14225 * @return {string} a string giving the name of the calendar 14226 */ 14227 PersianDate.prototype.getCalendar = function() { 14228 return "persian"; 14229 }; 14230 14231 // register with the factory method 14232 IDate._constructors["persian"] = PersianDate; 14233 14234 14235 /*< PersianAlgoCal.js */ 14236 /* 14237 * persian.js - Represent a Persian algorithmic calendar object. 14238 * 14239 * Copyright © 2014-2015, JEDLSoft 14240 * 14241 * Licensed under the Apache License, Version 2.0 (the "License"); 14242 * you may not use this file except in compliance with the License. 14243 * You may obtain a copy of the License at 14244 * 14245 * http://www.apache.org/licenses/LICENSE-2.0 14246 * 14247 * Unless required by applicable law or agreed to in writing, software 14248 * distributed under the License is distributed on an "AS IS" BASIS, 14249 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14250 * 14251 * See the License for the specific language governing permissions and 14252 * limitations under the License. 14253 */ 14254 14255 14256 /* !depends ilib.js Calendar.js MathUtils.js */ 14257 14258 14259 /** 14260 * @class 14261 * Construct a new Persian algorithmic calendar object. This class encodes information about 14262 * a Persian algorithmic calendar.<p> 14263 * 14264 * 14265 * @constructor 14266 * @extends Calendar 14267 */ 14268 var PersianAlgoCal = function() { 14269 this.type = "persian-algo"; 14270 }; 14271 14272 /** 14273 * @private 14274 * @const 14275 * @type Array.<number> 14276 * the lengths of each month 14277 */ 14278 PersianAlgoCal.monthLengths = [ 14279 31, // Farvardin 14280 31, // Ordibehesht 14281 31, // Khordad 14282 31, // Tir 14283 31, // Mordad 14284 31, // Shahrivar 14285 30, // Mehr 14286 30, // Aban 14287 30, // Azar 14288 30, // Dey 14289 30, // Bahman 14290 29 // Esfand 14291 ]; 14292 14293 /** 14294 * Return the number of months in the given year. The number of months in a year varies 14295 * for some luni-solar calendars because in some years, an extra month is needed to extend the 14296 * days in a year to an entire solar year. The month is represented as a 1-based number 14297 * where 1=first month, 2=second month, etc. 14298 * 14299 * @param {number} year a year for which the number of months is sought 14300 * @return {number} The number of months in the given year 14301 */ 14302 PersianAlgoCal.prototype.getNumMonths = function(year) { 14303 return 12; 14304 }; 14305 14306 /** 14307 * Return the number of days in a particular month in a particular year. This function 14308 * can return a different number for a month depending on the year because of things 14309 * like leap years. 14310 * 14311 * @param {number} month the month for which the length is sought 14312 * @param {number} year the year within which that month can be found 14313 * @return {number} the number of days within the given month in the given year 14314 */ 14315 PersianAlgoCal.prototype.getMonLength = function(month, year) { 14316 if (month !== 12 || !this.isLeapYear(year)) { 14317 return PersianAlgoCal.monthLengths[month-1]; 14318 } else { 14319 // Month 12, Esfand, has 30 days instead of 29 in leap years 14320 return 30; 14321 } 14322 }; 14323 14324 /** 14325 * Return the equivalent year in the 2820 year cycle that begins on 14326 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 14327 * whereas the others do not specifically. This cycle can be used as 14328 * a proxy for other years outside of the cycle by shifting them into 14329 * the cycle. 14330 * @param {number} year year to find the equivalent cycle year for 14331 * @returns {number} the equivalent cycle year 14332 */ 14333 PersianAlgoCal.prototype.equivalentCycleYear = function(year) { 14334 var y = year - (year >= 0 ? 474 : 473); 14335 return MathUtils.mod(y, 2820) + 474; 14336 }; 14337 14338 /** 14339 * Return true if the given year is a leap year in the Persian calendar. 14340 * The year parameter may be given as a number, or as a PersAlgoDate object. 14341 * @param {number} year the year for which the leap year information is being sought 14342 * @return {boolean} true if the given year is a leap year 14343 */ 14344 PersianAlgoCal.prototype.isLeapYear = function(year) { 14345 return (MathUtils.mod((this.equivalentCycleYear(year) + 38) * 682, 2816) < 682); 14346 }; 14347 14348 /** 14349 * Return the type of this calendar. 14350 * 14351 * @return {string} the name of the type of this calendar 14352 */ 14353 PersianAlgoCal.prototype.getType = function() { 14354 return this.type; 14355 }; 14356 14357 /** 14358 * Return a date instance for this calendar type using the given 14359 * options. 14360 * @param {Object} options options controlling the construction of 14361 * the date instance 14362 * @return {IDate} a date appropriate for this calendar type 14363 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 14364 */ 14365 PersianAlgoCal.prototype.newDateInstance = function (options) { 14366 return new PersianAlgoDate(options); 14367 }; 14368 14369 /* register this calendar for the factory method */ 14370 Calendar._constructors["persian-algo"] = PersianAlgoCal; 14371 14372 14373 /*< PersAlgoRataDie.js */ 14374 /* 14375 * PersAlsoRataDie.js - Represent an RD date in the Persian algorithmic calendar 14376 * 14377 * Copyright © 2014-2015, JEDLSoft 14378 * 14379 * Licensed under the Apache License, Version 2.0 (the "License"); 14380 * you may not use this file except in compliance with the License. 14381 * You may obtain a copy of the License at 14382 * 14383 * http://www.apache.org/licenses/LICENSE-2.0 14384 * 14385 * Unless required by applicable law or agreed to in writing, software 14386 * distributed under the License is distributed on an "AS IS" BASIS, 14387 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14388 * 14389 * See the License for the specific language governing permissions and 14390 * limitations under the License. 14391 */ 14392 14393 /* !depends 14394 PersianAlgoCal.js 14395 MathUtils.js 14396 RataDie.js 14397 */ 14398 14399 14400 /** 14401 * @class 14402 * Construct a new Persian RD date number object. The constructor parameters can 14403 * contain any of the following properties: 14404 * 14405 * <ul> 14406 * <li><i>unixtime<i> - sets the time of this instance according to the given 14407 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14408 * 14409 * <li><i>julianday</i> - sets the time of this instance according to the given 14410 * Julian Day instance or the Julian Day given as a float 14411 * 14412 * <li><i>year</i> - any integer, including 0 14413 * 14414 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14415 * 14416 * <li><i>day</i> - 1 to 31 14417 * 14418 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14419 * is always done with an unambiguous 24 hour representation 14420 * 14421 * <li><i>minute</i> - 0 to 59 14422 * 14423 * <li><i>second</i> - 0 to 59 14424 * 14425 * <li><i>millisecond</i> - 0 to 999 14426 * 14427 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14428 * </ul> 14429 * 14430 * If the constructor is called with another Persian date instance instead of 14431 * a parameter block, the other instance acts as a parameter block and its 14432 * settings are copied into the current instance.<p> 14433 * 14434 * If the constructor is called with no arguments at all or if none of the 14435 * properties listed above are present, then the RD is calculate based on 14436 * the current date at the time of instantiation. <p> 14437 * 14438 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14439 * specified in the params, it is assumed that they have the smallest possible 14440 * value in the range for the property (zero or one).<p> 14441 * 14442 * 14443 * @private 14444 * @constructor 14445 * @extends RataDie 14446 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 14447 */ 14448 var PersAlgoRataDie = function(params) { 14449 this.cal = params && params.cal || new PersianAlgoCal(); 14450 this.rd = NaN; 14451 RataDie.call(this, params); 14452 }; 14453 14454 PersAlgoRataDie.prototype = new RataDie(); 14455 PersAlgoRataDie.prototype.parent = RataDie; 14456 PersAlgoRataDie.prototype.constructor = PersAlgoRataDie; 14457 14458 /** 14459 * The difference between a zero Julian day and the first Persian date 14460 * @private 14461 * @type number 14462 */ 14463 PersAlgoRataDie.prototype.epoch = 1948319.5; 14464 14465 /** 14466 * @private 14467 * @const 14468 * @type Array.<number> 14469 * the cumulative lengths of each month, for a non-leap year 14470 */ 14471 PersAlgoRataDie.cumMonthLengths = [ 14472 0, // Farvardin 14473 31, // Ordibehesht 14474 62, // Khordad 14475 93, // Tir 14476 124, // Mordad 14477 155, // Shahrivar 14478 186, // Mehr 14479 216, // Aban 14480 246, // Azar 14481 276, // Dey 14482 306, // Bahman 14483 336, // Esfand 14484 365 14485 ]; 14486 14487 /** 14488 * Calculate the Rata Die (fixed day) number of the given date from the 14489 * date components. 14490 * 14491 * @protected 14492 * @param {Object} date the date components to calculate the RD from 14493 */ 14494 PersAlgoRataDie.prototype._setDateComponents = function(date) { 14495 var year = this.cal.equivalentCycleYear(date.year); 14496 var y = date.year - (date.year >= 0 ? 474 : 473); 14497 var rdOfYears = 1029983 * Math.floor(y/2820) + 365 * (year - 1) + Math.floor((682 * year - 110) / 2816); 14498 var dayInYear = (date.month > 1 ? PersAlgoRataDie.cumMonthLengths[date.month-1] : 0) + date.day; 14499 var rdtime = (date.hour * 3600000 + 14500 date.minute * 60000 + 14501 date.second * 1000 + 14502 date.millisecond) / 14503 86400000; 14504 14505 /* 14506 // console.log("getRataDie: converting " + JSON.stringify(this)); 14507 console.log("getRataDie: year is " + year); 14508 console.log("getRataDie: rd of years is " + rdOfYears); 14509 console.log("getRataDie: day in year is " + dayInYear); 14510 console.log("getRataDie: rdtime is " + rdtime); 14511 console.log("getRataDie: rd is " + (rdOfYears + dayInYear + rdtime)); 14512 */ 14513 14514 this.rd = rdOfYears + dayInYear + rdtime; 14515 }; 14516 14517 /** 14518 * Return the rd number of the particular day of the week on or before the 14519 * given rd. eg. The Sunday on or before the given rd. 14520 * @private 14521 * @param {number} rd the rata die date of the reference date 14522 * @param {number} dayOfWeek the day of the week that is being sought relative 14523 * to the current date 14524 * @return {number} the rd of the day of the week 14525 */ 14526 PersAlgoRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 14527 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 14528 }; 14529 14530 14531 /*< PersianAlgoDate.js */ 14532 /* 14533 * PersianAlgoDate.js - Represent a date in the Persian algorithmic calendar 14534 * 14535 * Copyright © 2014-2015, JEDLSoft 14536 * 14537 * Licensed under the Apache License, Version 2.0 (the "License"); 14538 * you may not use this file except in compliance with the License. 14539 * You may obtain a copy of the License at 14540 * 14541 * http://www.apache.org/licenses/LICENSE-2.0 14542 * 14543 * Unless required by applicable law or agreed to in writing, software 14544 * distributed under the License is distributed on an "AS IS" BASIS, 14545 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14546 * 14547 * See the License for the specific language governing permissions and 14548 * limitations under the License. 14549 */ 14550 14551 /* !depends 14552 ilib.js 14553 Locale.js 14554 LocaleInfo.js 14555 TimeZone.js 14556 IDate.js 14557 PersianAlgoCal.js 14558 SearchUtils.js 14559 MathUtils.js 14560 PersAlgoRataDie.js 14561 */ 14562 14563 14564 14565 14566 /** 14567 * @class 14568 * 14569 * Construct a new Persian date object. The constructor parameters can 14570 * contain any of the following properties: 14571 * 14572 * <ul> 14573 * <li><i>unixtime<i> - sets the time of this instance according to the given 14574 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14575 * 14576 * <li><i>julianday</i> - sets the time of this instance according to the given 14577 * Julian Day instance or the Julian Day given as a float 14578 * 14579 * <li><i>year</i> - any integer, including 0 14580 * 14581 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14582 * 14583 * <li><i>day</i> - 1 to 31 14584 * 14585 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14586 * is always done with an unambiguous 24 hour representation 14587 * 14588 * <li><i>minute</i> - 0 to 59 14589 * 14590 * <li><i>second</i> - 0 to 59 14591 * 14592 * <li><i>millisecond</i> - 0 to 999 14593 * 14594 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 14595 * of this persian date. The date/time is kept in the local time. The time zone 14596 * is used later if this date is formatted according to a different time zone and 14597 * the difference has to be calculated, or when the date format has a time zone 14598 * component in it. 14599 * 14600 * <li><i>locale</i> - locale for this persian date. If the time zone is not 14601 * given, it can be inferred from this locale. For locales that span multiple 14602 * time zones, the one with the largest population is chosen as the one that 14603 * represents the locale. 14604 * 14605 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14606 * </ul> 14607 * 14608 * If the constructor is called with another Persian date instance instead of 14609 * a parameter block, the other instance acts as a parameter block and its 14610 * settings are copied into the current instance.<p> 14611 * 14612 * If the constructor is called with no arguments at all or if none of the 14613 * properties listed above 14614 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 14615 * components are 14616 * filled in with the current date at the time of instantiation. Note that if 14617 * you do not give the time zone when defaulting to the current time and the 14618 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 14619 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 14620 * Mean Time").<p> 14621 * 14622 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14623 * specified in the params, it is assumed that they have the smallest possible 14624 * value in the range for the property (zero or one).<p> 14625 * 14626 * 14627 * @constructor 14628 * @extends IDate 14629 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 14630 */ 14631 var PersianAlgoDate = function(params) { 14632 this.cal = new PersianAlgoCal(); 14633 this.timezone = "local"; 14634 14635 if (params) { 14636 if (params.locale) { 14637 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 14638 var li = new LocaleInfo(this.locale); 14639 this.timezone = li.getTimeZone(); 14640 } 14641 if (params.timezone) { 14642 this.timezone = params.timezone; 14643 } 14644 14645 if (params.year || params.month || params.day || params.hour || 14646 params.minute || params.second || params.millisecond ) { 14647 /** 14648 * Year in the Persian calendar. 14649 * @type number 14650 */ 14651 this.year = parseInt(params.year, 10) || 0; 14652 14653 /** 14654 * The month number, ranging from 1 to 12 14655 * @type number 14656 */ 14657 this.month = parseInt(params.month, 10) || 1; 14658 14659 /** 14660 * The day of the month. This ranges from 1 to 31. 14661 * @type number 14662 */ 14663 this.day = parseInt(params.day, 10) || 1; 14664 14665 /** 14666 * The hour of the day. This can be a number from 0 to 23, as times are 14667 * stored unambiguously in the 24-hour clock. 14668 * @type number 14669 */ 14670 this.hour = parseInt(params.hour, 10) || 0; 14671 14672 /** 14673 * The minute of the hours. Ranges from 0 to 59. 14674 * @type number 14675 */ 14676 this.minute = parseInt(params.minute, 10) || 0; 14677 14678 /** 14679 * The second of the minute. Ranges from 0 to 59. 14680 * @type number 14681 */ 14682 this.second = parseInt(params.second, 10) || 0; 14683 14684 /** 14685 * The millisecond of the second. Ranges from 0 to 999. 14686 * @type number 14687 */ 14688 this.millisecond = parseInt(params.millisecond, 10) || 0; 14689 14690 /** 14691 * The day of the year. Ranges from 1 to 366. 14692 * @type number 14693 */ 14694 this.dayOfYear = parseInt(params.dayOfYear, 10); 14695 14696 if (typeof(params.dst) === 'boolean') { 14697 this.dst = params.dst; 14698 } 14699 14700 this.rd = this.newRd(this); 14701 14702 // add the time zone offset to the rd to convert to UTC 14703 if (!this.tz) { 14704 this.tz = new TimeZone({id: this.timezone}); 14705 } 14706 // getOffsetMillis requires that this.year, this.rd, and this.dst 14707 // are set in order to figure out which time zone rules apply and 14708 // what the offset is at that point in the year 14709 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 14710 if (this.offset !== 0) { 14711 this.rd = this.newRd({ 14712 rd: this.rd.getRataDie() - this.offset 14713 }); 14714 } 14715 } 14716 } 14717 14718 if (!this.rd) { 14719 this.rd = this.newRd(params); 14720 this._calcDateComponents(); 14721 } 14722 }; 14723 14724 PersianAlgoDate.prototype = new IDate({noinstance: true}); 14725 PersianAlgoDate.prototype.parent = IDate; 14726 PersianAlgoDate.prototype.constructor = PersianAlgoDate; 14727 14728 /** 14729 * Return a new RD for this date type using the given params. 14730 * @protected 14731 * @param {Object=} params the parameters used to create this rata die instance 14732 * @returns {RataDie} the new RD instance for the given params 14733 */ 14734 PersianAlgoDate.prototype.newRd = function (params) { 14735 return new PersAlgoRataDie(params); 14736 }; 14737 14738 /** 14739 * Return the year for the given RD 14740 * @protected 14741 * @param {number} rd RD to calculate from 14742 * @returns {number} the year for the RD 14743 */ 14744 PersianAlgoDate.prototype._calcYear = function(rd) { 14745 var shiftedRd = rd - 173126; 14746 var numberOfCycles = Math.floor(shiftedRd / 1029983); 14747 var shiftedDayInCycle = MathUtils.mod(shiftedRd, 1029983); 14748 var yearInCycle = (shiftedDayInCycle === 1029982) ? 2820 : Math.floor((2816 * shiftedDayInCycle + 1031337) / 1028522); 14749 var year = 474 + 2820 * numberOfCycles + yearInCycle; 14750 return (year > 0) ? year : year - 1; 14751 }; 14752 14753 /** 14754 * @private 14755 * Calculate date components for the given RD date. 14756 */ 14757 PersianAlgoDate.prototype._calcDateComponents = function () { 14758 var remainder, 14759 rd = this.rd.getRataDie(); 14760 14761 this.year = this._calcYear(rd); 14762 14763 if (typeof(this.offset) === "undefined") { 14764 // now offset the RD by the time zone, then recalculate in case we were 14765 // near the year boundary 14766 if (!this.tz) { 14767 this.tz = new TimeZone({id: this.timezone}); 14768 } 14769 this.offset = this.tz.getOffsetMillis(this) / 86400000; 14770 } 14771 14772 if (this.offset !== 0) { 14773 rd += this.offset; 14774 this.year = this._calcYear(rd); 14775 } 14776 14777 //console.log("PersAlgoDate.calcComponent: calculating for rd " + rd); 14778 //console.log("PersAlgoDate.calcComponent: year is " + ret.year); 14779 var yearStart = this.newRd({ 14780 year: this.year, 14781 month: 1, 14782 day: 1, 14783 hour: 0, 14784 minute: 0, 14785 second: 0, 14786 millisecond: 0 14787 }); 14788 remainder = rd - yearStart.getRataDie() + 1; 14789 14790 this.dayOfYear = remainder; 14791 14792 //console.log("PersAlgoDate.calcComponent: remainder is " + remainder); 14793 14794 this.month = SearchUtils.bsearch(remainder, PersAlgoRataDie.cumMonthLengths); 14795 remainder -= PersAlgoRataDie.cumMonthLengths[this.month-1]; 14796 14797 //console.log("PersAlgoDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 14798 14799 this.day = Math.floor(remainder); 14800 remainder -= this.day; 14801 14802 //console.log("PersAlgoDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 14803 14804 // now convert to milliseconds for the rest of the calculation 14805 remainder = Math.round(remainder * 86400000); 14806 14807 this.hour = Math.floor(remainder/3600000); 14808 remainder -= this.hour * 3600000; 14809 14810 this.minute = Math.floor(remainder/60000); 14811 remainder -= this.minute * 60000; 14812 14813 this.second = Math.floor(remainder/1000); 14814 remainder -= this.second * 1000; 14815 14816 this.millisecond = remainder; 14817 }; 14818 14819 /** 14820 * Return the day of the week of this date. The day of the week is encoded 14821 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 14822 * 14823 * @return {number} the day of the week 14824 */ 14825 PersianAlgoDate.prototype.getDayOfWeek = function() { 14826 var rd = Math.floor(this.getRataDie()); 14827 return MathUtils.mod(rd-3, 7); 14828 }; 14829 14830 /** 14831 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 14832 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 14833 * December 31st is 365 in regular years, or 366 in leap years. 14834 * @return {number} the ordinal day of the year 14835 */ 14836 PersianAlgoDate.prototype.getDayOfYear = function() { 14837 return PersAlgoRataDie.cumMonthLengths[this.month-1] + this.day; 14838 }; 14839 14840 /** 14841 * Return the era for this date as a number. The value for the era for Persian 14842 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 14843 * persico or AP). 14844 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 14845 * there is a year 0, so any years that are negative or zero are BP. 14846 * @return {number} 1 if this date is in the common era, -1 if it is before the 14847 * common era 14848 */ 14849 PersianAlgoDate.prototype.getEra = function() { 14850 return (this.year < 1) ? -1 : 1; 14851 }; 14852 14853 /** 14854 * Return the name of the calendar that governs this date. 14855 * 14856 * @return {string} a string giving the name of the calendar 14857 */ 14858 PersianAlgoDate.prototype.getCalendar = function() { 14859 return "persian-algo"; 14860 }; 14861 14862 // register with the factory method 14863 IDate._constructors["persian-algo"] = PersianAlgoDate; 14864 14865 14866 /*< HanCal.js */ 14867 /* 14868 * han.js - Represent a Han Chinese Lunar calendar object. 14869 * 14870 * Copyright © 2014-2015, JEDLSoft 14871 * 14872 * Licensed under the Apache License, Version 2.0 (the "License"); 14873 * you may not use this file except in compliance with the License. 14874 * You may obtain a copy of the License at 14875 * 14876 * http://www.apache.org/licenses/LICENSE-2.0 14877 * 14878 * Unless required by applicable law or agreed to in writing, software 14879 * distributed under the License is distributed on an "AS IS" BASIS, 14880 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14881 * 14882 * See the License for the specific language governing permissions and 14883 * limitations under the License. 14884 */ 14885 14886 /* !depends 14887 ilib.js 14888 Calendar.js 14889 MathUtils.js 14890 Astro.js 14891 GregorianDate.js 14892 GregRataDie.js 14893 RataDie.js 14894 */ 14895 14896 14897 14898 14899 /** 14900 * @class 14901 * Construct a new Han algorithmic calendar object. This class encodes information about 14902 * a Han algorithmic calendar.<p> 14903 * 14904 * 14905 * @constructor 14906 * @param {Object=} params optional parameters to load the calendrical data 14907 * @extends Calendar 14908 */ 14909 var HanCal = function(params) { 14910 this.type = "han"; 14911 var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true; 14912 14913 Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) { 14914 if (params && typeof(params.callback) === 'function') { 14915 params.callback(this); 14916 } 14917 })); 14918 }; 14919 14920 /** 14921 * @protected 14922 * @static 14923 * @param {number} year 14924 * @param {number=} cycle 14925 * @return {number} 14926 */ 14927 HanCal._getElapsedYear = function(year, cycle) { 14928 var elapsedYear = year || 0; 14929 if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') { 14930 elapsedYear = 60 * cycle + year; 14931 } 14932 return elapsedYear; 14933 }; 14934 14935 /** 14936 * @protected 14937 * @static 14938 * @param {number} jd julian day to calculate from 14939 * @param {number} longitude longitude to seek 14940 * @returns {number} the julian day of the next time that the solar longitude 14941 * is a multiple of the given longitude 14942 */ 14943 HanCal._hanNextSolarLongitude = function(jd, longitude) { 14944 var tz = HanCal._chineseTZ(jd); 14945 var uni = Astro._universalFromLocal(jd, tz); 14946 var sol = Astro._nextSolarLongitude(uni, longitude); 14947 return Astro._localFromUniversal(sol, tz); 14948 }; 14949 14950 /** 14951 * @protected 14952 * @static 14953 * @param {number} jd julian day to calculate from 14954 * @returns {number} the major solar term for the julian day 14955 */ 14956 HanCal._majorSTOnOrAfter = function(jd) { 14957 var tz = HanCal._chineseTZ(jd); 14958 var uni = Astro._universalFromLocal(jd, tz); 14959 var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30)); 14960 return HanCal._hanNextSolarLongitude(jd, next); 14961 }; 14962 14963 /** 14964 * @protected 14965 * @static 14966 * @param {number} year the year for which the leap year information is being sought 14967 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14968 * cycle is not given, then the year should be given as elapsed years since the beginning 14969 * of the epoch 14970 */ 14971 HanCal._solsticeBefore = function (year, cycle) { 14972 var elapsedYear = HanCal._getElapsedYear(year, cycle); 14973 var gregyear = elapsedYear - 2697; 14974 var rd = new GregRataDie({ 14975 year: gregyear-1, 14976 month: 12, 14977 day: 15, 14978 hour: 0, 14979 minute: 0, 14980 second: 0, 14981 millisecond: 0 14982 }); 14983 return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch); 14984 }; 14985 14986 /** 14987 * @protected 14988 * @static 14989 * @param {number} jd julian day to calculate from 14990 * @returns {number} the current major solar term 14991 */ 14992 HanCal._chineseTZ = function(jd) { 14993 var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch); 14994 return year < 1929 ? 465.6666666666666666 : 480; 14995 }; 14996 14997 /** 14998 * @protected 14999 * @static 15000 * @param {number} jd julian day to calculate from 15001 * @returns {number} the julian day of next new moon on or after the given julian day date 15002 */ 15003 HanCal._newMoonOnOrAfter = function(jd) { 15004 var tz = HanCal._chineseTZ(jd); 15005 var uni = Astro._universalFromLocal(jd, tz); 15006 var moon = Astro._newMoonAtOrAfter(uni); 15007 // floor to the start of the julian day 15008 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 15009 }; 15010 15011 /** 15012 * @protected 15013 * @static 15014 * @param {number} jd julian day to calculate from 15015 * @returns {number} the julian day of previous new moon before the given julian day date 15016 */ 15017 HanCal._newMoonBefore = function(jd) { 15018 var tz = HanCal._chineseTZ(jd); 15019 var uni = Astro._universalFromLocal(jd, tz); 15020 var moon = Astro._newMoonBefore(uni); 15021 // floor to the start of the julian day 15022 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 15023 }; 15024 15025 /** 15026 * @static 15027 * @protected 15028 * @param {number} year the year for which the leap year information is being sought 15029 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15030 * cycle is not given, then the year should be given as elapsed years since the beginning 15031 * of the epoch 15032 */ 15033 HanCal._leapYearCalc = function(year, cycle) { 15034 var ret = { 15035 elapsedYear: HanCal._getElapsedYear(year, cycle) 15036 }; 15037 ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear); 15038 ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1); 15039 // ceil to the end of the julian day 15040 ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1)); 15041 ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2)); 15042 15043 return ret; 15044 }; 15045 15046 /** 15047 * @protected 15048 * @static 15049 * @param {number} jd julian day to calculate from 15050 * @returns {number} the current major solar term 15051 */ 15052 HanCal._currentMajorST = function(jd) { 15053 var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd))); 15054 return MathUtils.amod(2 + Math.floor(s/30), 12); 15055 }; 15056 15057 /** 15058 * @protected 15059 * @static 15060 * @param {number} jd julian day to calculate from 15061 * @returns {boolean} true if there is no major solar term in the same year 15062 */ 15063 HanCal._noMajorST = function(jd) { 15064 return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1)); 15065 }; 15066 15067 /** 15068 * Return the number of months in the given year. The number of months in a year varies 15069 * for some luni-solar calendars because in some years, an extra month is needed to extend the 15070 * days in a year to an entire solar year. The month is represented as a 1-based number 15071 * where 1=first month, 2=second month, etc. 15072 * 15073 * @param {number} year a year for which the number of months is sought 15074 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15075 * cycle is not given, then the year should be given as elapsed years since the beginning 15076 * of the epoch 15077 * @return {number} The number of months in the given year 15078 */ 15079 HanCal.prototype.getNumMonths = function(year, cycle) { 15080 return this.isLeapYear(year, cycle) ? 13 : 12; 15081 }; 15082 15083 /** 15084 * Return the number of days in a particular month in a particular year. This function 15085 * can return a different number for a month depending on the year because of things 15086 * like leap years. 15087 * 15088 * @param {number} month the elapsed month for which the length is sought 15089 * @param {number} year the elapsed year within which that month can be found 15090 * @return {number} the number of days within the given month in the given year 15091 */ 15092 HanCal.prototype.getMonLength = function(month, year) { 15093 // distance between two new moons in Nanjing China 15094 var calc = HanCal._leapYearCalc(year); 15095 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29); 15096 var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1); 15097 return postNewMoon - priorNewMoon; 15098 }; 15099 15100 /** 15101 * Return the equivalent year in the 2820 year cycle that begins on 15102 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 15103 * whereas the others do not specifically. This cycle can be used as 15104 * a proxy for other years outside of the cycle by shifting them into 15105 * the cycle. 15106 * @param {number} year year to find the equivalent cycle year for 15107 * @returns {number} the equivalent cycle year 15108 */ 15109 HanCal.prototype.equivalentCycleYear = function(year) { 15110 var y = year - (year >= 0 ? 474 : 473); 15111 return MathUtils.mod(y, 2820) + 474; 15112 }; 15113 15114 /** 15115 * Return true if the given year is a leap year in the Han calendar. 15116 * If the year is given as a year/cycle combination, then the year should be in the 15117 * range [1,60] and the given cycle is the cycle in which the year is located. If 15118 * the year is greater than 60, then 15119 * it represents the total number of years elapsed in the proleptic calendar since 15120 * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this 15121 * case, the cycle parameter is ignored. 15122 * 15123 * @param {number} year the year for which the leap year information is being sought 15124 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15125 * cycle is not given, then the year should be given as elapsed years since the beginning 15126 * of the epoch 15127 * @return {boolean} true if the given year is a leap year 15128 */ 15129 HanCal.prototype.isLeapYear = function(year, cycle) { 15130 var calc = HanCal._leapYearCalc(year, cycle); 15131 return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15132 }; 15133 15134 /** 15135 * Return the month of the year that is the leap month. If the given year is 15136 * not a leap year, then this method will return -1. 15137 * 15138 * @param {number} year the year for which the leap year information is being sought 15139 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15140 * cycle is not given, then the year should be given as elapsed years since the beginning 15141 * of the epoch 15142 * @return {number} the number of the month that is doubled in this leap year, or -1 15143 * if this is not a leap year 15144 */ 15145 HanCal.prototype.getLeapMonth = function(year, cycle) { 15146 var calc = HanCal._leapYearCalc(year, cycle); 15147 15148 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) { 15149 return -1; // no leap month 15150 } 15151 15152 // search between rd1 and rd2 for the first month with no major solar term. That is our leap month. 15153 var month = 0; 15154 var m = HanCal._newMoonOnOrAfter(calc.m1+1); 15155 while (!HanCal._noMajorST(m)) { 15156 month++; 15157 m = HanCal._newMoonOnOrAfter(m+1); 15158 } 15159 15160 // return the number of the month that is doubled 15161 return month; 15162 }; 15163 15164 /** 15165 * Return the date of Chinese New Years in the given calendar year. 15166 * 15167 * @param {number} year the Chinese year for which the new year information is being sought 15168 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15169 * cycle is not given, then the year should be given as elapsed years since the beginning 15170 * of the epoch 15171 * @return {number} the julian day of the beginning of the given year 15172 */ 15173 HanCal.prototype.newYears = function(year, cycle) { 15174 var calc = HanCal._leapYearCalc(year, cycle); 15175 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15176 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 && 15177 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 15178 return HanCal._newMoonOnOrAfter(m2+1); 15179 } 15180 return m2; 15181 }; 15182 15183 /** 15184 * Return the type of this calendar. 15185 * 15186 * @return {string} the name of the type of this calendar 15187 */ 15188 HanCal.prototype.getType = function() { 15189 return this.type; 15190 }; 15191 15192 /** 15193 * Return a date instance for this calendar type using the given 15194 * options. 15195 * @param {Object} options options controlling the construction of 15196 * the date instance 15197 * @return {HanDate} a date appropriate for this calendar type 15198 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 15199 */ 15200 HanCal.prototype.newDateInstance = function (options) { 15201 return new HanDate(options); 15202 }; 15203 15204 /* register this calendar for the factory method */ 15205 Calendar._constructors["han"] = HanCal; 15206 15207 15208 /*< HanRataDie.js */ 15209 /* 15210 * HanDate.js - Represent a date in the Han algorithmic calendar 15211 * 15212 * Copyright © 2014-2015, JEDLSoft 15213 * 15214 * Licensed under the Apache License, Version 2.0 (the "License"); 15215 * you may not use this file except in compliance with the License. 15216 * You may obtain a copy of the License at 15217 * 15218 * http://www.apache.org/licenses/LICENSE-2.0 15219 * 15220 * Unless required by applicable law or agreed to in writing, software 15221 * distributed under the License is distributed on an "AS IS" BASIS, 15222 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15223 * 15224 * See the License for the specific language governing permissions and 15225 * limitations under the License. 15226 */ 15227 15228 /* !depends 15229 ilib.js 15230 HanCal.js 15231 MathUtils.js 15232 RataDie.js 15233 */ 15234 15235 15236 /** 15237 * Construct a new Han RD date number object. The constructor parameters can 15238 * contain any of the following properties: 15239 * 15240 * <ul> 15241 * <li><i>unixtime<i> - sets the time of this instance according to the given 15242 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15243 * 15244 * <li><i>julianday</i> - sets the time of this instance according to the given 15245 * Julian Day instance or the Julian Day given as a float 15246 * 15247 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15248 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15249 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15250 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15251 * to 60 and treated as if it were a year in the regular 60-year cycle. 15252 * 15253 * <li><i>year</i> - any integer, including 0 15254 * 15255 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15256 * 15257 * <li><i>day</i> - 1 to 31 15258 * 15259 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15260 * is always done with an unambiguous 24 hour representation 15261 * 15262 * <li><i>minute</i> - 0 to 59 15263 * 15264 * <li><i>second</i> - 0 to 59 15265 * 15266 * <li><i>millisecond</i> - 0 to 999 15267 * 15268 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15269 * </ul> 15270 * 15271 * If the constructor is called with another Han date instance instead of 15272 * a parameter block, the other instance acts as a parameter block and its 15273 * settings are copied into the current instance.<p> 15274 * 15275 * If the constructor is called with no arguments at all or if none of the 15276 * properties listed above are present, then the RD is calculate based on 15277 * the current date at the time of instantiation. <p> 15278 * 15279 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15280 * specified in the params, it is assumed that they have the smallest possible 15281 * value in the range for the property (zero or one).<p> 15282 * 15283 * 15284 * @private 15285 * @class 15286 * @constructor 15287 * @extends RataDie 15288 * @param {Object=} params parameters that govern the settings and behaviour of this Han RD date 15289 */ 15290 var HanRataDie = function(params) { 15291 this.rd = NaN; 15292 if (params && params.cal) { 15293 this.cal = params.cal; 15294 RataDie.call(this, params); 15295 if (params && typeof(params.callback) === 'function') { 15296 params.callback(this); 15297 } 15298 } else { 15299 new HanCal({ 15300 sync: params && params.sync, 15301 loadParams: params && params.loadParams, 15302 callback: ilib.bind(this, function(c) { 15303 this.cal = c; 15304 RataDie.call(this, params); 15305 if (params && typeof(params.callback) === 'function') { 15306 params.callback(this); 15307 } 15308 }) 15309 }); 15310 } 15311 }; 15312 15313 HanRataDie.prototype = new RataDie(); 15314 HanRataDie.prototype.parent = RataDie; 15315 HanRataDie.prototype.constructor = HanRataDie; 15316 15317 /** 15318 * The difference between a zero Julian day and the first Han date 15319 * which is February 15, -2636 (Gregorian). 15320 * @private 15321 * @type number 15322 */ 15323 HanRataDie.epoch = 758325.5; 15324 15325 /** 15326 * Calculate the Rata Die (fixed day) number of the given date from the 15327 * date components. 15328 * 15329 * @protected 15330 * @param {Object} date the date components to calculate the RD from 15331 */ 15332 HanRataDie.prototype._setDateComponents = function(date) { 15333 var calc = HanCal._leapYearCalc(date.year, date.cycle); 15334 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15335 var newYears; 15336 this.leapYear = (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12); 15337 if (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 15338 newYears = HanCal._newMoonOnOrAfter(m2+1); 15339 } else { 15340 newYears = m2; 15341 } 15342 15343 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + date.month * 29); // this is a julian day 15344 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(priorNewMoon)); 15345 this.leapMonth = (this.leapYear && HanCal._noMajorST(priorNewMoon) && !this.priorLeapMonth); 15346 15347 var rdtime = (date.hour * 3600000 + 15348 date.minute * 60000 + 15349 date.second * 1000 + 15350 date.millisecond) / 15351 86400000; 15352 15353 /* 15354 console.log("getRataDie: converting " + JSON.stringify(date) + " to an RD"); 15355 console.log("getRataDie: year is " + date.year + " plus cycle " + date.cycle); 15356 console.log("getRataDie: isLeapYear is " + this.leapYear); 15357 console.log("getRataDie: priorNewMoon is " + priorNewMoon); 15358 console.log("getRataDie: day in month is " + date.day); 15359 console.log("getRataDie: rdtime is " + rdtime); 15360 console.log("getRataDie: rd is " + (priorNewMoon + date.day - 1 + rdtime)); 15361 */ 15362 15363 this.rd = priorNewMoon + date.day - 1 + rdtime - RataDie.gregorianEpoch; 15364 }; 15365 15366 /** 15367 * Return the rd number of the particular day of the week on or before the 15368 * given rd. eg. The Sunday on or before the given rd. 15369 * @private 15370 * @param {number} rd the rata die date of the reference date 15371 * @param {number} dayOfWeek the day of the week that is being sought relative 15372 * to the current date 15373 * @return {number} the rd of the day of the week 15374 */ 15375 HanRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 15376 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 15377 }; 15378 15379 /** 15380 * @protected 15381 * @static 15382 * @param {number} jd1 first julian day 15383 * @param {number} jd2 second julian day 15384 * @returns {boolean} true if there is a leap month earlier in the same year 15385 * as the given months 15386 */ 15387 HanRataDie._priorLeapMonth = function(jd1, jd2) { 15388 return jd2 >= jd1 && 15389 (HanRataDie._priorLeapMonth(jd1, HanCal._newMoonBefore(jd2)) || 15390 HanCal._noMajorST(jd2)); 15391 }; 15392 15393 15394 15395 /*< HanDate.js */ 15396 /* 15397 * HanDate.js - Represent a date in the Han algorithmic calendar 15398 * 15399 * Copyright © 2014-2015, JEDLSoft 15400 * 15401 * Licensed under the Apache License, Version 2.0 (the "License"); 15402 * you may not use this file except in compliance with the License. 15403 * You may obtain a copy of the License at 15404 * 15405 * http://www.apache.org/licenses/LICENSE-2.0 15406 * 15407 * Unless required by applicable law or agreed to in writing, software 15408 * distributed under the License is distributed on an "AS IS" BASIS, 15409 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15410 * 15411 * See the License for the specific language governing permissions and 15412 * limitations under the License. 15413 */ 15414 15415 /* !depends 15416 ilib.js 15417 IDate.js 15418 GregorianDate.js 15419 HanCal.js 15420 Astro.js 15421 JSUtils.js 15422 MathUtils.js 15423 LocaleInfo.js 15424 Locale.js 15425 TimeZone.js 15426 HanRataDie.js 15427 RataDie.js 15428 */ 15429 15430 15431 15432 15433 /** 15434 * @class 15435 * 15436 * Construct a new Han date object. The constructor parameters can 15437 * contain any of the following properties: 15438 * 15439 * <ul> 15440 * <li><i>unixtime<i> - sets the time of this instance according to the given 15441 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15442 * 15443 * <li><i>julianday</i> - sets the time of this instance according to the given 15444 * Julian Day instance or the Julian Day given as a float 15445 * 15446 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15447 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15448 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15449 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15450 * to 60 and treated as if it were a year in the regular 60-year cycle. 15451 * 15452 * <li><i>year</i> - any integer, including 0 15453 * 15454 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15455 * 15456 * <li><i>day</i> - 1 to 31 15457 * 15458 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15459 * is always done with an unambiguous 24 hour representation 15460 * 15461 * <li><i>minute</i> - 0 to 59 15462 * 15463 * <li><i>second</i> - 0 to 59 15464 * 15465 * <li><i>millisecond</i> - 0 to 999 15466 * 15467 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 15468 * of this han date. The date/time is kept in the local time. The time zone 15469 * is used later if this date is formatted according to a different time zone and 15470 * the difference has to be calculated, or when the date format has a time zone 15471 * component in it. 15472 * 15473 * <li><i>locale</i> - locale for this han date. If the time zone is not 15474 * given, it can be inferred from this locale. For locales that span multiple 15475 * time zones, the one with the largest population is chosen as the one that 15476 * represents the locale. 15477 * 15478 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15479 * </ul> 15480 * 15481 * If the constructor is called with another Han date instance instead of 15482 * a parameter block, the other instance acts as a parameter block and its 15483 * settings are copied into the current instance.<p> 15484 * 15485 * If the constructor is called with no arguments at all or if none of the 15486 * properties listed above 15487 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 15488 * components are 15489 * filled in with the current date at the time of instantiation. Note that if 15490 * you do not give the time zone when defaulting to the current time and the 15491 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 15492 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 15493 * Mean Time").<p> 15494 * 15495 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15496 * specified in the params, it is assumed that they have the smallest possible 15497 * value in the range for the property (zero or one).<p> 15498 * 15499 * 15500 * @constructor 15501 * @extends Date 15502 * @param {Object=} params parameters that govern the settings and behaviour of this Han date 15503 */ 15504 var HanDate = function(params) { 15505 this.timezone = "local"; 15506 if (params) { 15507 if (params.locale) { 15508 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 15509 var li = new LocaleInfo(this.locale); 15510 this.timezone = li.getTimeZone(); 15511 } 15512 if (params.timezone) { 15513 this.timezone = params.timezone; 15514 } 15515 } 15516 15517 new HanCal({ 15518 sync: params && typeof(params) === 'boolean' ? params.sync : true, 15519 loadParams: params && params.loadParams, 15520 callback: ilib.bind(this, function (cal) { 15521 this.cal = cal; 15522 15523 if (params && (params.year || params.month || params.day || params.hour || 15524 params.minute || params.second || params.millisecond || params.cycle || params.cycleYear)) { 15525 if (typeof(params.cycle) !== 'undefined') { 15526 /** 15527 * Cycle number in the Han calendar. 15528 * @type number 15529 */ 15530 this.cycle = parseInt(params.cycle, 10) || 0; 15531 15532 var year = (typeof(params.year) !== 'undefined' ? parseInt(params.year, 10) : parseInt(params.cycleYear, 10)) || 0; 15533 15534 /** 15535 * Year in the Han calendar. 15536 * @type number 15537 */ 15538 this.year = HanCal._getElapsedYear(year, this.cycle); 15539 } else { 15540 if (typeof(params.year) !== 'undefined') { 15541 this.year = parseInt(params.year, 10) || 0; 15542 this.cycle = Math.floor((this.year - 1) / 60); 15543 } else { 15544 this.year = this.cycle = 0; 15545 } 15546 } 15547 15548 /** 15549 * The month number, ranging from 1 to 13 15550 * @type number 15551 */ 15552 this.month = parseInt(params.month, 10) || 1; 15553 15554 /** 15555 * The day of the month. This ranges from 1 to 30. 15556 * @type number 15557 */ 15558 this.day = parseInt(params.day, 10) || 1; 15559 15560 /** 15561 * The hour of the day. This can be a number from 0 to 23, as times are 15562 * stored unambiguously in the 24-hour clock. 15563 * @type number 15564 */ 15565 this.hour = parseInt(params.hour, 10) || 0; 15566 15567 /** 15568 * The minute of the hours. Ranges from 0 to 59. 15569 * @type number 15570 */ 15571 this.minute = parseInt(params.minute, 10) || 0; 15572 15573 /** 15574 * The second of the minute. Ranges from 0 to 59. 15575 * @type number 15576 */ 15577 this.second = parseInt(params.second, 10) || 0; 15578 15579 /** 15580 * The millisecond of the second. Ranges from 0 to 999. 15581 * @type number 15582 */ 15583 this.millisecond = parseInt(params.millisecond, 10) || 0; 15584 15585 // derived properties 15586 15587 /** 15588 * Year in the cycle of the Han calendar 15589 * @type number 15590 */ 15591 this.cycleYear = MathUtils.amod(this.year, 60); 15592 15593 /** 15594 * The day of the year. Ranges from 1 to 384. 15595 * @type number 15596 */ 15597 this.dayOfYear = parseInt(params.dayOfYear, 10); 15598 15599 if (typeof(params.dst) === 'boolean') { 15600 this.dst = params.dst; 15601 } 15602 15603 this.newRd({ 15604 cal: this.cal, 15605 cycle: this.cycle, 15606 year: this.year, 15607 month: this.month, 15608 day: this.day, 15609 hour: this.hour, 15610 minute: this.minute, 15611 second: this.second, 15612 millisecond: this.millisecond, 15613 sync: params && typeof(params.sync) === 'boolean' ? params.sync : true, 15614 loadParams: params && params.loadParams, 15615 callback: ilib.bind(this, function (rd) { 15616 if (rd) { 15617 this.rd = rd; 15618 15619 // add the time zone offset to the rd to convert to UTC 15620 if (!this.tz) { 15621 this.tz = new TimeZone({id: this.timezone}); 15622 } 15623 // getOffsetMillis requires that this.year, this.rd, and this.dst 15624 // are set in order to figure out which time zone rules apply and 15625 // what the offset is at that point in the year 15626 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 15627 if (this.offset !== 0) { 15628 this.rd = this.newRd({ 15629 cal: this.cal, 15630 rd: this.rd.getRataDie() - this.offset 15631 }); 15632 this._calcLeap(); 15633 } else { 15634 // re-use the derived properties from the RD calculations 15635 this.leapMonth = this.rd.leapMonth; 15636 this.priorLeapMonth = this.rd.priorLeapMonth; 15637 this.leapYear = this.rd.leapYear; 15638 } 15639 } 15640 15641 if (!this.rd) { 15642 this.rd = this.newRd(JSUtils.merge(params || {}, { 15643 cal: this.cal 15644 })); 15645 this._calcDateComponents(); 15646 } 15647 15648 if (params && typeof(params.onLoad) === 'function') { 15649 params.onLoad(this); 15650 } 15651 }) 15652 }); 15653 } else { 15654 if (!this.rd) { 15655 this.rd = this.newRd(JSUtils.merge(params || {}, { 15656 cal: this.cal 15657 })); 15658 this._calcDateComponents(); 15659 } 15660 15661 if (params && typeof(params.onLoad) === 'function') { 15662 params.onLoad(this); 15663 } 15664 } 15665 }) 15666 }); 15667 15668 }; 15669 15670 HanDate.prototype = new IDate({noinstance: true}); 15671 HanDate.prototype.parent = IDate; 15672 HanDate.prototype.constructor = HanDate; 15673 15674 /** 15675 * Return a new RD for this date type using the given params. 15676 * @protected 15677 * @param {Object=} params the parameters used to create this rata die instance 15678 * @returns {RataDie} the new RD instance for the given params 15679 */ 15680 HanDate.prototype.newRd = function (params) { 15681 return new HanRataDie(params); 15682 }; 15683 15684 /** 15685 * Return the year for the given RD 15686 * @protected 15687 * @param {number} rd RD to calculate from 15688 * @returns {number} the year for the RD 15689 */ 15690 HanDate.prototype._calcYear = function(rd) { 15691 var gregdate = new GregorianDate({ 15692 rd: rd, 15693 timezone: this.timezone 15694 }); 15695 var hanyear = gregdate.year + 2697; 15696 var newYears = this.cal.newYears(hanyear); 15697 return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); 15698 }; 15699 15700 /** 15701 * @private 15702 * Calculate the leap year and months from the RD. 15703 */ 15704 HanDate.prototype._calcLeap = function() { 15705 var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 15706 15707 var calc = HanCal._leapYearCalc(this.year); 15708 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15709 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15710 15711 var newYears = (this.leapYear && 15712 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15713 HanCal._newMoonOnOrAfter(m2+1) : m2; 15714 15715 var m = HanCal._newMoonBefore(jd + 1); 15716 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 15717 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 15718 }; 15719 15720 /** 15721 * @private 15722 * Calculate date components for the given RD date. 15723 */ 15724 HanDate.prototype._calcDateComponents = function () { 15725 var remainder, 15726 jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 15727 15728 // console.log("HanDate._calcDateComponents: calculating for jd " + jd); 15729 15730 if (typeof(this.offset) === "undefined") { 15731 // now offset the jd by the time zone, then recalculate in case we were 15732 // near the year boundary 15733 if (!this.tz) { 15734 this.tz = new TimeZone({id: this.timezone}); 15735 } 15736 this.offset = this.tz.getOffsetMillis(this) / 86400000; 15737 } 15738 15739 if (this.offset !== 0) { 15740 jd += this.offset; 15741 } 15742 15743 // use the Gregorian calendar objects as a convenient way to short-cut some 15744 // of the date calculations 15745 15746 var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); 15747 this.year = gregyear + 2697; 15748 var calc = HanCal._leapYearCalc(this.year); 15749 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15750 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15751 var newYears = (this.leapYear && 15752 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15753 HanCal._newMoonOnOrAfter(m2+1) : m2; 15754 15755 // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If 15756 // so, then the Han year is actually the previous one 15757 if (jd < newYears) { 15758 this.year--; 15759 calc = HanCal._leapYearCalc(this.year); 15760 m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15761 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15762 newYears = (this.leapYear && 15763 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15764 HanCal._newMoonOnOrAfter(m2+1) : m2; 15765 } 15766 // month is elapsed month, not the month number + leap month boolean 15767 var m = HanCal._newMoonBefore(jd + 1); 15768 this.month = Math.round((m - calc.m1) / 29.530588853000001); 15769 15770 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 15771 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 15772 15773 this.cycle = Math.floor((this.year - 1) / 60); 15774 this.cycleYear = MathUtils.amod(this.year, 60); 15775 this.day = Astro._floorToJD(jd) - m + 1; 15776 15777 /* 15778 console.log("HanDate._calcDateComponents: year is " + this.year); 15779 console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); 15780 console.log("HanDate._calcDateComponents: cycle is " + this.cycle); 15781 console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); 15782 console.log("HanDate._calcDateComponents: month is " + this.month); 15783 console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); 15784 console.log("HanDate._calcDateComponents: day is " + this.day); 15785 */ 15786 15787 // floor to the start of the julian day 15788 remainder = jd - Astro._floorToJD(jd); 15789 15790 // console.log("HanDate._calcDateComponents: time remainder is " + remainder); 15791 15792 // now convert to milliseconds for the rest of the calculation 15793 remainder = Math.round(remainder * 86400000); 15794 15795 this.hour = Math.floor(remainder/3600000); 15796 remainder -= this.hour * 3600000; 15797 15798 this.minute = Math.floor(remainder/60000); 15799 remainder -= this.minute * 60000; 15800 15801 this.second = Math.floor(remainder/1000); 15802 remainder -= this.second * 1000; 15803 15804 this.millisecond = remainder; 15805 }; 15806 15807 /** 15808 * Return the year within the Chinese cycle of this date. Cycles are 60 15809 * years long, and the value returned from this method is the number of the year 15810 * within this cycle. The year returned from getYear() is the total elapsed 15811 * years since the beginning of the Chinese epoch and does not include 15812 * the cycles. 15813 * 15814 * @return {number} the year within the current Chinese cycle 15815 */ 15816 HanDate.prototype.getCycleYears = function() { 15817 return this.cycleYear; 15818 }; 15819 15820 /** 15821 * Return the Chinese cycle number of this date. Cycles are 60 years long, 15822 * and the value returned from getCycleYear() is the number of the year 15823 * within this cycle. The year returned from getYear() is the total elapsed 15824 * years since the beginning of the Chinese epoch and does not include 15825 * the cycles. 15826 * 15827 * @return {number} the current Chinese cycle 15828 */ 15829 HanDate.prototype.getCycles = function() { 15830 return this.cycle; 15831 }; 15832 15833 /** 15834 * Return whether the year of this date is a leap year in the Chinese Han 15835 * calendar. 15836 * 15837 * @return {boolean} true if the year of this date is a leap year in the 15838 * Chinese Han calendar. 15839 */ 15840 HanDate.prototype.isLeapYear = function() { 15841 return this.leapYear; 15842 }; 15843 15844 /** 15845 * Return whether the month of this date is a leap month in the Chinese Han 15846 * calendar. 15847 * 15848 * @return {boolean} true if the month of this date is a leap month in the 15849 * Chinese Han calendar. 15850 */ 15851 HanDate.prototype.isLeapMonth = function() { 15852 return this.leapMonth; 15853 }; 15854 15855 /** 15856 * Return the day of the week of this date. The day of the week is encoded 15857 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 15858 * 15859 * @return {number} the day of the week 15860 */ 15861 HanDate.prototype.getDayOfWeek = function() { 15862 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 15863 return MathUtils.mod(rd, 7); 15864 }; 15865 15866 /** 15867 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 15868 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 15869 * December 31st is 365 in regular years, or 366 in leap years. 15870 * @return {number} the ordinal day of the year 15871 */ 15872 HanDate.prototype.getDayOfYear = function() { 15873 var newYears = this.cal.newYears(this.year); 15874 var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); 15875 return priorNewMoon - newYears + this.day; 15876 }; 15877 15878 /** 15879 * Return the era for this date as a number. The value for the era for Han 15880 * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno 15881 * persico or AP). 15882 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han calendar, 15883 * there is a year 0, so any years that are negative or zero are BP. 15884 * @return {number} 1 if this date is in the common era, -1 if it is before the 15885 * common era 15886 */ 15887 HanDate.prototype.getEra = function() { 15888 return (this.year < 1) ? -1 : 1; 15889 }; 15890 15891 /** 15892 * Return the name of the calendar that governs this date. 15893 * 15894 * @return {string} a string giving the name of the calendar 15895 */ 15896 HanDate.prototype.getCalendar = function() { 15897 return "han"; 15898 }; 15899 15900 // register with the factory method 15901 IDate._constructors["han"] = HanDate; 15902 15903 15904 /*< EthiopicCal.js */ 15905 /* 15906 * ethiopic.js - Represent a Ethiopic calendar object. 15907 * 15908 * Copyright © 2015, JEDLSoft 15909 * 15910 * Licensed under the Apache License, Version 2.0 (the "License"); 15911 * you may not use this file except in compliance with the License. 15912 * You may obtain a copy of the License at 15913 * 15914 * http://www.apache.org/licenses/LICENSE-2.0 15915 * 15916 * Unless required by applicable law or agreed to in writing, software 15917 * distributed under the License is distributed on an "AS IS" BASIS, 15918 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15919 * 15920 * See the License for the specific language governing permissions and 15921 * limitations under the License. 15922 */ 15923 15924 /* !depends ilib.js Calendar.js Utils.js MathUtils.js */ 15925 15926 15927 15928 /** 15929 * @class 15930 * Construct a new Ethiopic calendar object. This class encodes information about 15931 * a Ethiopic calendar.<p> 15932 * 15933 * 15934 * @constructor 15935 * @extends Calendar 15936 */ 15937 var EthiopicCal = function() { 15938 this.type = "ethiopic"; 15939 }; 15940 15941 /** 15942 * Return the number of months in the given year. The number of months in a year varies 15943 * for lunar calendars because in some years, an extra month is needed to extend the 15944 * days in a year to an entire solar year. The month is represented as a 1-based number 15945 * where 1=Maskaram, 2=Teqemt, etc. until 13=Paguemen. 15946 * 15947 * @param {number} year a year for which the number of months is sought 15948 */ 15949 EthiopicCal.prototype.getNumMonths = function(year) { 15950 return 13; 15951 }; 15952 15953 /** 15954 * Return the number of days in a particular month in a particular year. This function 15955 * can return a different number for a month depending on the year because of things 15956 * like leap years. 15957 * 15958 * @param {number|string} month the month for which the length is sought 15959 * @param {number} year the year within which that month can be found 15960 * @return {number} the number of days within the given month in the given year 15961 */ 15962 EthiopicCal.prototype.getMonLength = function(month, year) { 15963 var m = month; 15964 switch (typeof(m)) { 15965 case "string": 15966 m = parseInt(m, 10); 15967 break; 15968 case "function": 15969 case "object": 15970 case "undefined": 15971 return 30; 15972 break; 15973 } 15974 if (m < 13) { 15975 return 30; 15976 } else { 15977 return this.isLeapYear(year) ? 6 : 5; 15978 } 15979 }; 15980 15981 /** 15982 * Return true if the given year is a leap year in the Ethiopic calendar. 15983 * The year parameter may be given as a number, or as a JulDate object. 15984 * @param {number|EthiopicDate|string} year the year for which the leap year information is being sought 15985 * @return {boolean} true if the given year is a leap year 15986 */ 15987 EthiopicCal.prototype.isLeapYear = function(year) { 15988 var y = year; 15989 switch (typeof(y)) { 15990 case "string": 15991 y = parseInt(y, 10); 15992 break; 15993 case "object": 15994 if (typeof(y.year) !== "number") { // in case it is an ilib.Date object 15995 return false; 15996 } 15997 y = y.year; 15998 break; 15999 case "function": 16000 case "undefined": 16001 return false; 16002 break; 16003 } 16004 return MathUtils.mod(y, 4) === 3; 16005 }; 16006 16007 /** 16008 * Return the type of this calendar. 16009 * 16010 * @return {string} the name of the type of this calendar 16011 */ 16012 EthiopicCal.prototype.getType = function() { 16013 return this.type; 16014 }; 16015 16016 /** 16017 * Return a date instance for this calendar type using the given 16018 * options. 16019 * @param {Object} options options controlling the construction of 16020 * the date instance 16021 * @return {IDate} a date appropriate for this calendar type 16022 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 16023 */ 16024 EthiopicCal.prototype.newDateInstance = function (options) { 16025 return new EthiopicDate(options); 16026 }; 16027 16028 /* register this calendar for the factory method */ 16029 Calendar._constructors["ethiopic"] = EthiopicCal; 16030 16031 16032 /*< EthiopicRataDie.js */ 16033 /* 16034 * EthiopicRataDie.js - Represent an RD date in the Ethiopic calendar 16035 * 16036 * Copyright © 2015, JEDLSoft 16037 * 16038 * Licensed under the Apache License, Version 2.0 (the "License"); 16039 * you may not use this file except in compliance with the License. 16040 * You may obtain a copy of the License at 16041 * 16042 * http://www.apache.org/licenses/LICENSE-2.0 16043 * 16044 * Unless required by applicable law or agreed to in writing, software 16045 * distributed under the License is distributed on an "AS IS" BASIS, 16046 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16047 * 16048 * See the License for the specific language governing permissions and 16049 * limitations under the License. 16050 */ 16051 16052 /* !depends 16053 ilib.js 16054 EthiopicCal.js 16055 RataDie.js 16056 */ 16057 16058 16059 /** 16060 * @class 16061 * Construct a new Ethiopic RD date number object. The constructor parameters can 16062 * contain any of the following properties: 16063 * 16064 * <ul> 16065 * <li><i>unixtime<i> - sets the time of this instance according to the given 16066 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 16067 * 16068 * <li><i>julianday</i> - sets the time of this instance according to the given 16069 * Julian Day instance or the Julian Day given as a float 16070 * 16071 * <li><i>year</i> - any integer, including 0 16072 * 16073 * <li><i>month</i> - 1 to 12, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 16074 * 16075 * <li><i>day</i> - 1 to 30 16076 * 16077 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16078 * is always done with an unambiguous 24 hour representation 16079 * 16080 * <li><i>minute</i> - 0 to 59 16081 * 16082 * <li><i>second</i> - 0 to 59 16083 * 16084 * <li><i>millisecond</i> - 0 to 999 16085 * 16086 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16087 * </ul> 16088 * 16089 * If the constructor is called with another Ethiopic date instance instead of 16090 * a parameter block, the other instance acts as a parameter block and its 16091 * settings are copied into the current instance.<p> 16092 * 16093 * If the constructor is called with no arguments at all or if none of the 16094 * properties listed above are present, then the RD is calculate based on 16095 * the current date at the time of instantiation. <p> 16096 * 16097 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16098 * specified in the params, it is assumed that they have the smallest possible 16099 * value in the range for the property (zero or one).<p> 16100 * 16101 * 16102 * @private 16103 * @constructor 16104 * @extends RataDie 16105 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic RD date 16106 */ 16107 var EthiopicRataDie = function(params) { 16108 this.cal = params && params.cal || new EthiopicCal(); 16109 this.rd = NaN; 16110 RataDie.call(this, params); 16111 }; 16112 16113 EthiopicRataDie.prototype = new RataDie(); 16114 EthiopicRataDie.prototype.parent = RataDie; 16115 EthiopicRataDie.prototype.constructor = EthiopicRataDie; 16116 16117 /** 16118 * The difference between the zero Julian day and the first Ethiopic date 16119 * of Friday, August 29, 8 CE Julian at 6:00am UTC.<p> 16120 * 16121 * See <a href="http://us.wow.com/wiki/Time_in_Ethiopia?s_chn=90&s_pt=aolsem&v_t=aolsem" 16122 * Time in Ethiopia</a> for information about how time is handled in Ethiopia. 16123 * 16124 * @protected 16125 * @type number 16126 */ 16127 EthiopicRataDie.prototype.epoch = 1724219.75; 16128 16129 /** 16130 * Calculate the Rata Die (fixed day) number of the given date from the 16131 * date components. 16132 * 16133 * @protected 16134 * @param {Object} date the date components to calculate the RD from 16135 */ 16136 EthiopicRataDie.prototype._setDateComponents = function(date) { 16137 var year = date.year; 16138 var years = 365 * (year - 1) + Math.floor(year/4); 16139 var dayInYear = (date.month-1) * 30 + date.day; 16140 var rdtime = (date.hour * 3600000 + 16141 date.minute * 60000 + 16142 date.second * 1000 + 16143 date.millisecond) / 16144 86400000; 16145 16146 /* 16147 console.log("calcRataDie: converting " + JSON.stringify(parts)); 16148 console.log("getRataDie: year is " + years); 16149 console.log("getRataDie: day in year is " + dayInYear); 16150 console.log("getRataDie: rdtime is " + rdtime); 16151 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 16152 */ 16153 16154 this.rd = years + dayInYear + rdtime; 16155 }; 16156 16157 16158 16159 /*< EthiopicDate.js */ 16160 /* 16161 * EthiopicDate.js - Represent a date in the Ethiopic calendar 16162 * 16163 * Copyright © 2015, JEDLSoft 16164 * 16165 * Licensed under the Apache License, Version 2.0 (the "License"); 16166 * you may not use this file except in compliance with the License. 16167 * You may obtain a copy of the License at 16168 * 16169 * http://www.apache.org/licenses/LICENSE-2.0 16170 * 16171 * Unless required by applicable law or agreed to in writing, software 16172 * distributed under the License is distributed on an "AS IS" BASIS, 16173 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16174 * 16175 * See the License for the specific language governing permissions and 16176 * limitations under the License. 16177 */ 16178 16179 /* !depends 16180 ilib.js 16181 IDate.js 16182 EthiopicCal.js 16183 MathUtils.js 16184 Locale.js 16185 LocaleInfo.js 16186 TimeZone.js 16187 EthiopicRataDie.js 16188 */ 16189 16190 16191 16192 /** 16193 * @class 16194 * Construct a new date object for the Ethiopic Calendar. The constructor can be called 16195 * with a parameter object that contains any of the following properties: 16196 * 16197 * <ul> 16198 * <li><i>unixtime<i> - sets the time of this instance according to the given 16199 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 16200 * <li><i>julianday</i> - the Julian Day to set into this date 16201 * <li><i>year</i> - any integer 16202 * <li><i>month</i> - 1 to 13, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 16203 * <li><i>day</i> - 1 to 30 16204 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16205 * is always done with an unambiguous 24 hour representation 16206 * <li><i>minute</i> - 0 to 59 16207 * <li><i>second</i> - 0 to 59 16208 * <li><i>millisecond<i> - 0 to 999 16209 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 16210 * of this ethiopic date. The date/time is kept in the local time. The time zone 16211 * is used later if this date is formatted according to a different time zone and 16212 * the difference has to be calculated, or when the date format has a time zone 16213 * component in it. 16214 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 16215 * given, it can be inferred from this locale. For locales that span multiple 16216 * time zones, the one with the largest population is chosen as the one that 16217 * represents the locale. 16218 * 16219 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16220 * </ul> 16221 * 16222 * If called with another Ethiopic date argument, the date components of the given 16223 * date are copied into the current one.<p> 16224 * 16225 * If the constructor is called with no arguments at all or if none of the 16226 * properties listed above 16227 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 16228 * components are 16229 * filled in with the current date at the time of instantiation. Note that if 16230 * you do not give the time zone when defaulting to the current time and the 16231 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 16232 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 16233 * Mean Time").<p> 16234 * 16235 * 16236 * @constructor 16237 * @extends IDate 16238 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic date 16239 */ 16240 var EthiopicDate = function(params) { 16241 this.cal = new EthiopicCal(); 16242 16243 if (params) { 16244 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 16245 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 16246 return; 16247 } 16248 if (params.locale) { 16249 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 16250 var li = new LocaleInfo(this.locale); 16251 this.timezone = li.getTimeZone(); 16252 } 16253 if (params.timezone) { 16254 this.timezone = params.timezone; 16255 } 16256 16257 if (params.year || params.month || params.day || params.hour || 16258 params.minute || params.second || params.millisecond ) { 16259 /** 16260 * Year in the Ethiopic calendar. 16261 * @type number 16262 */ 16263 this.year = parseInt(params.year, 10) || 0; 16264 /** 16265 * The month number, ranging from 1 (Maskaram) to 13 (Paguemen). 16266 * @type number 16267 */ 16268 this.month = parseInt(params.month, 10) || 1; 16269 /** 16270 * The day of the month. This ranges from 1 to 30. 16271 * @type number 16272 */ 16273 this.day = parseInt(params.day, 10) || 1; 16274 /** 16275 * The hour of the day. This can be a number from 0 to 23, as times are 16276 * stored unambiguously in the 24-hour clock. 16277 * @type number 16278 */ 16279 this.hour = parseInt(params.hour, 10) || 0; 16280 /** 16281 * The minute of the hours. Ranges from 0 to 59. 16282 * @type number 16283 */ 16284 this.minute = parseInt(params.minute, 10) || 0; 16285 /** 16286 * The second of the minute. Ranges from 0 to 59. 16287 * @type number 16288 */ 16289 this.second = parseInt(params.second, 10) || 0; 16290 /** 16291 * The millisecond of the second. Ranges from 0 to 999. 16292 * @type number 16293 */ 16294 this.millisecond = parseInt(params.millisecond, 10) || 0; 16295 16296 /** 16297 * The day of the year. Ranges from 1 to 366. 16298 * @type number 16299 */ 16300 this.dayOfYear = parseInt(params.dayOfYear, 10); 16301 16302 if (typeof(params.dst) === 'boolean') { 16303 this.dst = params.dst; 16304 } 16305 16306 this.rd = this.newRd(this); 16307 16308 // add the time zone offset to the rd to convert to UTC 16309 if (!this.tz) { 16310 this.tz = new TimeZone({id: this.timezone}); 16311 } 16312 // getOffsetMillis requires that this.year, this.rd, and this.dst 16313 // are set in order to figure out which time zone rules apply and 16314 // what the offset is at that point in the year 16315 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 16316 if (this.offset !== 0) { 16317 this.rd = this.newRd({ 16318 rd: this.rd.getRataDie() - this.offset 16319 }); 16320 } 16321 } 16322 } 16323 16324 if (!this.rd) { 16325 this.rd = this.newRd(params); 16326 this._calcDateComponents(); 16327 } 16328 }; 16329 16330 EthiopicDate.prototype = new IDate({ noinstance: true }); 16331 EthiopicDate.prototype.parent = IDate; 16332 EthiopicDate.prototype.constructor = EthiopicDate; 16333 16334 /** 16335 * Return a new RD for this date type using the given params. 16336 * @protected 16337 * @param {Object=} params the parameters used to create this rata die instance 16338 * @returns {RataDie} the new RD instance for the given params 16339 */ 16340 EthiopicDate.prototype.newRd = function (params) { 16341 return new EthiopicRataDie(params); 16342 }; 16343 16344 /** 16345 * Return the year for the given RD 16346 * @protected 16347 * @param {number} rd RD to calculate from 16348 * @returns {number} the year for the RD 16349 */ 16350 EthiopicDate.prototype._calcYear = function(rd) { 16351 var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); 16352 16353 return year; 16354 }; 16355 16356 /** 16357 * Calculate date components for the given RD date. 16358 * @protected 16359 */ 16360 EthiopicDate.prototype._calcDateComponents = function () { 16361 var remainder, 16362 cumulative, 16363 rd = this.rd.getRataDie(); 16364 16365 this.year = this._calcYear(rd); 16366 16367 if (typeof(this.offset) === "undefined") { 16368 this.year = this._calcYear(rd); 16369 16370 // now offset the RD by the time zone, then recalculate in case we were 16371 // near the year boundary 16372 if (!this.tz) { 16373 this.tz = new TimeZone({id: this.timezone}); 16374 } 16375 this.offset = this.tz.getOffsetMillis(this) / 86400000; 16376 } 16377 16378 if (this.offset !== 0) { 16379 rd += this.offset; 16380 this.year = this._calcYear(rd); 16381 } 16382 16383 var jan1 = this.newRd({ 16384 year: this.year, 16385 month: 1, 16386 day: 1, 16387 hour: 0, 16388 minute: 0, 16389 second: 0, 16390 millisecond: 0 16391 }); 16392 remainder = rd + 1 - jan1.getRataDie(); 16393 16394 this.month = Math.floor((remainder-1)/30) + 1; 16395 remainder = remainder - (this.month-1) * 30; 16396 16397 this.day = Math.floor(remainder); 16398 remainder -= this.day; 16399 // now convert to milliseconds for the rest of the calculation 16400 remainder = Math.round(remainder * 86400000); 16401 16402 this.hour = Math.floor(remainder/3600000); 16403 remainder -= this.hour * 3600000; 16404 16405 this.minute = Math.floor(remainder/60000); 16406 remainder -= this.minute * 60000; 16407 16408 this.second = Math.floor(remainder/1000); 16409 remainder -= this.second * 1000; 16410 16411 this.millisecond = remainder; 16412 }; 16413 16414 /** 16415 * Return the day of the week of this date. The day of the week is encoded 16416 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16417 * 16418 * @return {number} the day of the week 16419 */ 16420 EthiopicDate.prototype.getDayOfWeek = function() { 16421 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16422 return MathUtils.mod(rd-4, 7); 16423 }; 16424 16425 /** 16426 * Return the name of the calendar that governs this date. 16427 * 16428 * @return {string} a string giving the name of the calendar 16429 */ 16430 EthiopicDate.prototype.getCalendar = function() { 16431 return "ethiopic"; 16432 }; 16433 16434 //register with the factory method 16435 IDate._constructors["ethiopic"] = EthiopicDate; 16436 16437 16438 16439 /*< CopticCal.js */ 16440 /* 16441 * coptic.js - Represent a Coptic calendar object. 16442 * 16443 * Copyright © 2015, JEDLSoft 16444 * 16445 * Licensed under the Apache License, Version 2.0 (the "License"); 16446 * you may not use this file except in compliance with the License. 16447 * You may obtain a copy of the License at 16448 * 16449 * http://www.apache.org/licenses/LICENSE-2.0 16450 * 16451 * Unless required by applicable law or agreed to in writing, software 16452 * distributed under the License is distributed on an "AS IS" BASIS, 16453 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16454 * 16455 * See the License for the specific language governing permissions and 16456 * limitations under the License. 16457 */ 16458 16459 16460 /* !depends ilib.js Calendar.js Locale.js Utils.js EthiopicCal.js */ 16461 16462 16463 /** 16464 * @class 16465 * Construct a new Coptic calendar object. This class encodes information about 16466 * a Coptic calendar.<p> 16467 * 16468 * 16469 * @constructor 16470 * @extends EthiopicCal 16471 */ 16472 var CopticCal = function() { 16473 this.type = "coptic"; 16474 }; 16475 16476 CopticCal.prototype = new EthiopicCal(); 16477 CopticCal.prototype.parent = EthiopicCal; 16478 CopticCal.prototype.constructor = CopticCal; 16479 16480 /** 16481 * Return a date instance for this calendar type using the given 16482 * options. 16483 * @param {Object} options options controlling the construction of 16484 * the date instance 16485 * @return {IDate} a date appropriate for this calendar type 16486 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 16487 */ 16488 CopticCal.prototype.newDateInstance = function (options) { 16489 return new CopticDate(options); 16490 }; 16491 16492 /* register this calendar for the factory method */ 16493 Calendar._constructors["coptic"] = CopticCal; 16494 16495 16496 /*< CopticRataDie.js */ 16497 /* 16498 * CopticRataDie.js - Represent an RD date in the Coptic calendar 16499 * 16500 * Copyright © 2015, JEDLSoft 16501 * 16502 * Licensed under the Apache License, Version 2.0 (the "License"); 16503 * you may not use this file except in compliance with the License. 16504 * You may obtain a copy of the License at 16505 * 16506 * http://www.apache.org/licenses/LICENSE-2.0 16507 * 16508 * Unless required by applicable law or agreed to in writing, software 16509 * distributed under the License is distributed on an "AS IS" BASIS, 16510 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16511 * 16512 * See the License for the specific language governing permissions and 16513 * limitations under the License. 16514 */ 16515 16516 /* !depends 16517 ilib.js 16518 CopticCal.js 16519 JSUtils.js 16520 EthiopicRataDie.js 16521 */ 16522 16523 16524 /** 16525 * @class 16526 * Construct a new Coptic RD date number object. The constructor parameters can 16527 * contain any of the following properties: 16528 * 16529 * <ul> 16530 * <li><i>unixtime<i> - sets the time of this instance according to the given 16531 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 16532 * 16533 * <li><i>julianday</i> - sets the time of this instance according to the given 16534 * Julian Day instance or the Julian Day given as a float 16535 * 16536 * <li><i>year</i> - any integer, including 0 16537 * 16538 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 16539 * 16540 * <li><i>day</i> - 1 to 30 16541 * 16542 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16543 * is always done with an unambiguous 24 hour representation 16544 * 16545 * <li><i>minute</i> - 0 to 59 16546 * 16547 * <li><i>second</i> - 0 to 59 16548 * 16549 * <li><i>millisecond</i> - 0 to 999 16550 * 16551 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16552 * </ul> 16553 * 16554 * If the constructor is called with another Coptic date instance instead of 16555 * a parameter block, the other instance acts as a parameter block and its 16556 * settings are copied into the current instance.<p> 16557 * 16558 * If the constructor is called with no arguments at all or if none of the 16559 * properties listed above are present, then the RD is calculate based on 16560 * the current date at the time of instantiation. <p> 16561 * 16562 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16563 * specified in the params, it is assumed that they have the smallest possible 16564 * value in the range for the property (zero or one).<p> 16565 * 16566 * 16567 * @private 16568 * @constructor 16569 * @extends EthiopicRataDie 16570 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic RD date 16571 */ 16572 var CopticRataDie = function(params) { 16573 this.cal = params && params.cal || new CopticCal(); 16574 this.rd = NaN; 16575 /** 16576 * The difference between the zero Julian day and the first Coptic date 16577 * of Friday, August 29, 284 CE Julian at 7:00am UTC. 16578 * @private 16579 * @type number 16580 */ 16581 this.epoch = 1825028.5; 16582 16583 var tmp = {}; 16584 if (params) { 16585 JSUtils.shallowCopy(params, tmp); 16586 } 16587 tmp.cal = this.cal; // override the cal parameter that may be passed in 16588 EthiopicRataDie.call(this, tmp); 16589 }; 16590 16591 CopticRataDie.prototype = new EthiopicRataDie(); 16592 CopticRataDie.prototype.parent = EthiopicRataDie; 16593 CopticRataDie.prototype.constructor = CopticRataDie; 16594 16595 16596 /*< CopticDate.js */ 16597 /* 16598 * CopticDate.js - Represent a date in the Coptic calendar 16599 * 16600 * Copyright © 2015, JEDLSoft 16601 * 16602 * Licensed under the Apache License, Version 2.0 (the "License"); 16603 * you may not use this file except in compliance with the License. 16604 * You may obtain a copy of the License at 16605 * 16606 * http://www.apache.org/licenses/LICENSE-2.0 16607 * 16608 * Unless required by applicable law or agreed to in writing, software 16609 * distributed under the License is distributed on an "AS IS" BASIS, 16610 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16611 * 16612 * See the License for the specific language governing permissions and 16613 * limitations under the License. 16614 */ 16615 16616 /* !depends 16617 ilib.js 16618 IDate.js 16619 CopticCal.js 16620 MathUtils.js 16621 JSUtils.js 16622 Locale.js 16623 LocaleInfo.js 16624 TimeZone.js 16625 EthiopicDate.js 16626 CopticRataDie.js 16627 */ 16628 16629 16630 16631 16632 /** 16633 * @class 16634 * Construct a new date object for the Coptic Calendar. The constructor can be called 16635 * with a parameter object that contains any of the following properties: 16636 * 16637 * <ul> 16638 * <li><i>unixtime<i> - sets the time of this instance according to the given 16639 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 16640 * <li><i>julianday</i> - the Julian Day to set into this date 16641 * <li><i>year</i> - any integer 16642 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 16643 * <li><i>day</i> - 1 to 30 16644 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16645 * is always done with an unambiguous 24 hour representation 16646 * <li><i>minute</i> - 0 to 59 16647 * <li><i>second</i> - 0 to 59 16648 * <li><i>millisecond<i> - 0 to 999 16649 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 16650 * of this coptic date. The date/time is kept in the local time. The time zone 16651 * is used later if this date is formatted according to a different time zone and 16652 * the difference has to be calculated, or when the date format has a time zone 16653 * component in it. 16654 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 16655 * given, it can be inferred from this locale. For locales that span multiple 16656 * time zones, the one with the largest population is chosen as the one that 16657 * represents the locale. 16658 * 16659 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16660 * </ul> 16661 * 16662 * If called with another Coptic date argument, the date components of the given 16663 * date are copied into the current one.<p> 16664 * 16665 * If the constructor is called with no arguments at all or if none of the 16666 * properties listed above 16667 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 16668 * components are 16669 * filled in with the current date at the time of instantiation. Note that if 16670 * you do not give the time zone when defaulting to the current time and the 16671 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 16672 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 16673 * Mean Time").<p> 16674 * 16675 * 16676 * @constructor 16677 * @extends EthiopicDate 16678 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic date 16679 */ 16680 var CopticDate = function(params) { 16681 this.rd = NaN; // clear these out so that the EthiopicDate constructor can set it 16682 EthiopicDate.call(this, params); 16683 this.cal = new CopticCal(); 16684 }; 16685 16686 CopticDate.prototype = new EthiopicDate({noinstance: true}); 16687 CopticDate.prototype.parent = EthiopicDate.prototype; 16688 CopticDate.prototype.constructor = CopticDate; 16689 16690 /** 16691 * Return a new RD for this date type using the given params. 16692 * @protected 16693 * @param {Object=} params the parameters used to create this rata die instance 16694 * @returns {RataDie} the new RD instance for the given params 16695 */ 16696 CopticDate.prototype.newRd = function (params) { 16697 return new CopticRataDie(params); 16698 }; 16699 16700 /** 16701 * Return the day of the week of this date. The day of the week is encoded 16702 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16703 * 16704 * @return {number} the day of the week 16705 */ 16706 CopticDate.prototype.getDayOfWeek = function() { 16707 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16708 return MathUtils.mod(rd-3, 7); 16709 }; 16710 16711 /** 16712 * Return the name of the calendar that governs this date. 16713 * 16714 * @return {string} a string giving the name of the calendar 16715 */ 16716 CopticDate.prototype.getCalendar = function() { 16717 return "coptic"; 16718 }; 16719 16720 //register with the factory method 16721 IDate._constructors["coptic"] = CopticDate; 16722 16723 16724 /*< CType.js */ 16725 /* 16726 * CType.js - Character type definitions 16727 * 16728 * Copyright © 2012-2015, JEDLSoft 16729 * 16730 * Licensed under the Apache License, Version 2.0 (the "License"); 16731 * you may not use this file except in compliance with the License. 16732 * You may obtain a copy of the License at 16733 * 16734 * http://www.apache.org/licenses/LICENSE-2.0 16735 * 16736 * Unless required by applicable law or agreed to in writing, software 16737 * distributed under the License is distributed on an "AS IS" BASIS, 16738 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16739 * 16740 * See the License for the specific language governing permissions and 16741 * limitations under the License. 16742 */ 16743 16744 // !depends ilib.js Locale.js SearchUtils.js Utils.js IString.js 16745 16746 // !data ctype 16747 16748 16749 /** 16750 * Provides a set of static routines that return information about characters. 16751 * These routines emulate the C-library ctype functions. The characters must be 16752 * encoded in utf-16, as no other charsets are currently supported. Only the first 16753 * character of the given string is tested. 16754 * @namespace 16755 */ 16756 var CType = {}; 16757 16758 16759 /** 16760 * Actual implementation for withinRange. Searches the given object for ranges. 16761 * The range names are taken from the Unicode range names in 16762 * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt 16763 * 16764 * <ul> 16765 * <li>Cn - Unassigned 16766 * <li>Lu - Uppercase_Letter 16767 * <li>Ll - Lowercase_Letter 16768 * <li>Lt - Titlecase_Letter 16769 * <li>Lm - Modifier_Letter 16770 * <li>Lo - Other_Letter 16771 * <li>Mn - Nonspacing_Mark 16772 * <li>Me - Enclosing_Mark 16773 * <li>Mc - Spacing_Mark 16774 * <li>Nd - Decimal_Number 16775 * <li>Nl - Letter_Number 16776 * <li>No - Other_Number 16777 * <li>Zs - Space_Separator 16778 * <li>Zl - Line_Separator 16779 * <li>Zp - Paragraph_Separator 16780 * <li>Cc - Control 16781 * <li>Cf - Format 16782 * <li>Co - Private_Use 16783 * <li>Cs - Surrogate 16784 * <li>Pd - Dash_Punctuation 16785 * <li>Ps - Open_Punctuation 16786 * <li>Pe - Close_Punctuation 16787 * <li>Pc - Connector_Punctuation 16788 * <li>Po - Other_Punctuation 16789 * <li>Sm - Math_Symbol 16790 * <li>Sc - Currency_Symbol 16791 * <li>Sk - Modifier_Symbol 16792 * <li>So - Other_Symbol 16793 * <li>Pi - Initial_Punctuation 16794 * <li>Pf - Final_Punctuation 16795 * </ul> 16796 * 16797 * @protected 16798 * @param {number} num code point of the character to examine 16799 * @param {string} rangeName the name of the range to check 16800 * @param {Object} obj object containing the character range data 16801 * @return {boolean} true if the first character is within the named 16802 * range 16803 */ 16804 CType._inRange = function(num, rangeName, obj) { 16805 var range, i; 16806 if (num < 0 || !rangeName || !obj) { 16807 return false; 16808 } 16809 16810 range = obj[rangeName]; 16811 if (!range) { 16812 return false; 16813 } 16814 16815 var compare = function(singlerange, target) { 16816 if (singlerange.length === 1) { 16817 return singlerange[0] - target; 16818 } else { 16819 return target < singlerange[0] ? singlerange[0] - target : 16820 (target > singlerange[1] ? singlerange[1] - target : 0); 16821 } 16822 }; 16823 var result = SearchUtils.bsearch(num, range, compare); 16824 return result < range.length && compare(range[result], num) === 0; 16825 }; 16826 16827 /** 16828 * Return whether or not the first character is within the named range 16829 * of Unicode characters. The valid list of range names are taken from 16830 * the Unicode 6.0 spec. Characters in all ranges of Unicode are supported, 16831 * including those supported in Javascript via UTF-16. Currently, this method 16832 * supports the following range names: 16833 * 16834 * <ul> 16835 * <li><i>ascii</i> - basic ASCII 16836 * <li><i>latin</i> - Latin, Latin Extended Additional, Latin-1 supplement, Latin Extended-C, Latin Extended-D, Latin Extended-E 16837 * <li><i>armenian</i> 16838 * <li><i>greek</i> - Greek, Greek Extended 16839 * <li><i>cyrillic</i> - Cyrillic, Cyrillic Extended-A, Cyrillic Extended-B, Cyrillic Supplement 16840 * <li><i>georgian</i> - Georgian, Georgian Supplement 16841 * <li><i>glagolitic</i> 16842 * <li><i>gothic</i> 16843 * <li><i>ogham</i> 16844 * <li><i>oldpersian</i> 16845 * <li><i>runic</i> 16846 * <li><i>ipa</i> - IPA, Phonetic Extensions, Phonetic Extensions Supplement 16847 * <li><i>phonetic</i> 16848 * <li><i>modifiertone</i> - Modifier Tone Letters 16849 * <li><i>spacing</i> 16850 * <li><i>diacritics</i> 16851 * <li><i>halfmarks</i> - Combining Half Marks 16852 * <li><i>small</i> - Small Form Variants 16853 * <li><i>bamum</i> - Bamum, Bamum Supplement 16854 * <li><i>ethiopic</i> - Ethiopic, Ethiopic Extended, Ethiopic Extended-A 16855 * <li><i>nko</i> 16856 * <li><i>osmanya</i> 16857 * <li><i>tifinagh</i> 16858 * <li><i>val</i> 16859 * <li><i>arabic</i> - Arabic, Arabic Supplement, Arabic Presentation Forms-A, 16860 * Arabic Presentation Forms-B, Arabic Mathematical Alphabetic Symbols 16861 * <li><i>carlan</i> 16862 * <li><i>hebrew</i> 16863 * <li><i>mandaic</i> 16864 * <li><i>samaritan</i> 16865 * <li><i>syriac</i> 16866 * <li><i>mongolian</i> 16867 * <li><i>phagspa</i> 16868 * <li><i>tibetan</i> 16869 * <li><i>bengali</i> 16870 * <li><i>devanagari</i> - Devanagari, Devanagari Extended 16871 * <li><i>gujarati</i> 16872 * <li><i>gurmukhi</i> 16873 * <li><i>kannada</i> 16874 * <li><i>lepcha</i> 16875 * <li><i>limbu</i> 16876 * <li><i>malayalam</i> 16877 * <li><i>meetaimayek</i> 16878 * <li><i>olchiki</i> 16879 * <li><i>oriya</i> 16880 * <li><i>saurashtra</i> 16881 * <li><i>sinhala</i> 16882 * <li><i>sylotinagri</i> - Syloti Nagri 16883 * <li><i>tamil</i> 16884 * <li><i>telugu</i> 16885 * <li><i>thaana</i> 16886 * <li><i>vedic</i> 16887 * <li><i>batak</i> 16888 * <li><i>balinese</i> 16889 * <li><i>buginese</i> 16890 * <li><i>cham</i> 16891 * <li><i>javanese</i> 16892 * <li><i>kayahli</i> 16893 * <li><i>khmer</i> 16894 * <li><i>lao</i> 16895 * <li><i>myanmar</i> - Myanmar, Myanmar Extended-A, Myanmar Extended-B 16896 * <li><i>newtailue</i> 16897 * <li><i>rejang</i> 16898 * <li><i>sundanese</i> - Sundanese, Sundanese Supplement 16899 * <li><i>taile</i> 16900 * <li><i>taitham</i> 16901 * <li><i>taiviet</i> 16902 * <li><i>thai</i> 16903 * <li><i>buhld</i> 16904 * <li><i>hanunoo</i> 16905 * <li><i>tagalog</i> 16906 * <li><i>tagbanwa</i> 16907 * <li><i>bopomofo</i> - Bopomofo, Bopomofo Extended 16908 * <li><i>cjk</i> - the CJK unified ideographs (Han), CJK Unified Ideographs 16909 * Extension A, CJK Unified Ideographs Extension B, CJK Unified Ideographs 16910 * Extension C, CJK Unified Ideographs Extension D, Ideographic Description 16911 * Characters (=isIdeo()) 16912 * <li><i>cjkcompatibility</i> - CJK Compatibility, CJK Compatibility 16913 * Ideographs, CJK Compatibility Forms, CJK Compatibility Ideographs Supplement 16914 * <li><i>cjkradicals</i> - the CJK radicals, KangXi radicals 16915 * <li><i>hangul</i> - Hangul Jamo, Hangul Syllables, Hangul Jamo Extended-A, 16916 * Hangul Jamo Extended-B, Hangul Compatibility Jamo 16917 * <li><i>cjkpunct</i> - CJK symbols and punctuation 16918 * <li><i>cjkstrokes</i> - CJK strokes 16919 * <li><i>hiragana</i> 16920 * <li><i>katakana</i> - Katakana, Katakana Phonetic Extensions, Kana Supplement 16921 * <li><i>kanbun</i> 16922 * <li><i>lisu</i> 16923 * <li><i>yi</i> - Yi Syllables, Yi Radicals 16924 * <li><i>cherokee</i> 16925 * <li><i>canadian</i> - Unified Canadian Aboriginal Syllabics, Unified Canadian 16926 * Aboriginal Syllabics Extended 16927 * <li><i>presentation</i> - Alphabetic presentation forms 16928 * <li><i>vertical</i> - Vertical Forms 16929 * <li><i>width</i> - Halfwidth and Fullwidth Forms 16930 * <li><i>punctuation</i> - General punctuation, Supplemental Punctuation 16931 * <li><i>box</i> - Box Drawing 16932 * <li><i>block</i> - Block Elements 16933 * <li><i>letterlike</i> - Letterlike symbols 16934 * <li><i>mathematical</i> - Mathematical alphanumeric symbols, Miscellaneous 16935 * Mathematical Symbols-A, Miscellaneous Mathematical Symbols-B 16936 * <li><i>enclosedalpha</i> - Enclosed alphanumerics, Enclosed Alphanumeric Supplement 16937 * <li><i>enclosedcjk</i> - Enclosed CJK letters and months, Enclosed Ideographic Supplement 16938 * <li><i>cjkcompatibility</i> - CJK compatibility 16939 * <li><i>apl</i> - APL symbols 16940 * <li><i>controlpictures</i> - Control pictures 16941 * <li><i>misc</i> - Miscellaneous technical 16942 * <li><i>ocr</i> - Optical character recognition (OCR) 16943 * <li><i>combining</i> - Combining Diacritical Marks, Combining Diacritical Marks 16944 * for Symbols, Combining Diacritical Marks Supplement, Combining Diacritical Marks Extended 16945 * <li><i>digits</i> - ASCII digits (=isDigit()) 16946 * <li><i>indicnumber</i> - Common Indic Number Forms 16947 * <li><i>numbers</i> - Number forms 16948 * <li><i>supersub</i> - Superscripts and Subscripts 16949 * <li><i>arrows</i> - Arrows, Miscellaneous Symbols and Arrows, Supplemental Arrows-A, 16950 * Supplemental Arrows-B, Supplemental Arrows-C 16951 * <li><i>operators</i> - Mathematical operators, supplemental 16952 * mathematical operators 16953 * <li><i>geometric</i> - Geometric shapes, Geometric shapes extended 16954 * <li><i>ancient</i> - Ancient symbols 16955 * <li><i>braille</i> - Braille patterns 16956 * <li><i>currency</i> - Currency symbols 16957 * <li><i>dingbats</i> 16958 * <li><i>gamesymbols</i> 16959 * <li><i>yijing</i> - Yijing Hexagram Symbols 16960 * <li><i>specials</i> 16961 * <li><i>variations</i> - Variation Selectors, Variation Selectors Supplement 16962 * <li><i>privateuse</i> - Private Use Area, Supplementary Private Use Area-A, 16963 * Supplementary Private Use Area-B 16964 * <li><i>supplementarya</i> - Supplementary private use area-A 16965 * <li><i>supplementaryb</i> - Supplementary private use area-B 16966 * <li><i>highsurrogates</i> - High Surrogates, High Private Use Surrogates 16967 * <li><i>lowsurrogates</i> 16968 * <li><i>reserved</i> 16969 * <li><i>noncharacters</i> 16970 * <li><i>copticnumber</i> - coptic epact numbers 16971 * <li><i>oldpermic</i> - old permic 16972 * <li><i>albanian</i> - albanian 16973 * <li><i>lineara</i> - linear a 16974 * <li><i>meroitic</i> - meroitic cursive 16975 * <li><i>oldnortharabian</i> - old north arabian 16976 * <li><i>oldhungarian</i> - Supplementary private use area-A 16977 * <li><i>sorasompeng</i> - sora sompeng 16978 * <li><i>warangciti</i> - warang citi 16979 * <li><i>paucinhau</i> - pau cin hau 16980 * <li><i>bassavah</i> - bassa vah 16981 * <li><i>pahawhhmong</i> - pahawh hmong 16982 * <li><i>shorthandformat</i> - shorthand format controls 16983 * <li><i>suttonsignwriting</i> - sutton signwriting 16984 * <li><i>pictographs</i> - miscellaneous symbols and pictographs, supplemental symbols and pictographs 16985 * <li><i>ornamentaldingbats</i> - ornamental dingbats 16986 * </ul><p> 16987 * 16988 * 16989 * @protected 16990 * @param {string|IString|number} ch character or code point to examine 16991 * @param {string} rangeName the name of the range to check 16992 * @return {boolean} true if the first character is within the named 16993 * range 16994 */ 16995 CType.withinRange = function(ch, rangeName) { 16996 if (!rangeName) { 16997 return false; 16998 } 16999 var num; 17000 switch (typeof(ch)) { 17001 case 'number': 17002 num = ch; 17003 break; 17004 case 'string': 17005 num = IString.toCodePoint(ch, 0); 17006 break; 17007 case 'undefined': 17008 return false; 17009 default: 17010 num = ch._toCodePoint(0); 17011 break; 17012 } 17013 17014 return CType._inRange(num, rangeName.toLowerCase(), ilib.data.ctype); 17015 }; 17016 17017 /** 17018 * @protected 17019 * @param {boolean} sync 17020 * @param {Object|undefined} loadParams 17021 * @param {function(*)|undefined} onLoad 17022 */ 17023 CType._init = function(sync, loadParams, onLoad) { 17024 CType._load("ctype", sync, loadParams, onLoad); 17025 }; 17026 17027 /** 17028 * @protected 17029 * @param {string} name 17030 * @param {boolean} sync 17031 * @param {Object|undefined} loadParams 17032 * @param {function(*)|undefined} onLoad 17033 */ 17034 CType._load = function (name, sync, loadParams, onLoad) { 17035 if (!ilib.data[name]) { 17036 var loadName = name ? name + ".json" : "CType.json"; 17037 Utils.loadData({ 17038 name: loadName, 17039 locale: "-", 17040 nonlocale: true, 17041 sync: sync, 17042 loadParams: loadParams, 17043 callback: ilib.bind(this, function(ct) { 17044 ilib.data[name] = ct; 17045 if (onLoad && typeof(onLoad) === 'function') { 17046 onLoad(ilib.data[name]); 17047 } 17048 }) 17049 }); 17050 } else { 17051 if (onLoad && typeof(onLoad) === 'function') { 17052 onLoad(ilib.data[name]); 17053 } 17054 } 17055 }; 17056 17057 17058 17059 /*< isDigit.js */ 17060 /* 17061 * isDigit.js - Character type is digit 17062 * 17063 * Copyright © 2012-2015, JEDLSoft 17064 * 17065 * Licensed under the Apache License, Version 2.0 (the "License"); 17066 * you may not use this file except in compliance with the License. 17067 * You may obtain a copy of the License at 17068 * 17069 * http://www.apache.org/licenses/LICENSE-2.0 17070 * 17071 * Unless required by applicable law or agreed to in writing, software 17072 * distributed under the License is distributed on an "AS IS" BASIS, 17073 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17074 * 17075 * See the License for the specific language governing permissions and 17076 * limitations under the License. 17077 */ 17078 17079 // !depends CType.js IString.js ilib.js 17080 17081 // !data ctype 17082 17083 17084 /** 17085 * Return whether or not the first character is a digit character in the 17086 * Latin script.<p> 17087 * 17088 * @static 17089 * @param {string|IString|number} ch character or code point to examine 17090 * @return {boolean} true if the first character is a digit character in the 17091 * Latin script. 17092 */ 17093 var isDigit = function (ch) { 17094 var num; 17095 switch (typeof(ch)) { 17096 case 'number': 17097 num = ch; 17098 break; 17099 case 'string': 17100 num = IString.toCodePoint(ch, 0); 17101 break; 17102 case 'undefined': 17103 return false; 17104 default: 17105 num = ch._toCodePoint(0); 17106 break; 17107 } 17108 return CType._inRange(num, 'digit', ilib.data.ctype); 17109 }; 17110 17111 /** 17112 * @protected 17113 * @param {boolean} sync 17114 * @param {Object|undefined} loadParams 17115 * @param {function(*)|undefined} onLoad 17116 */ 17117 isDigit._init = function (sync, loadParams, onLoad) { 17118 CType._init(sync, loadParams, onLoad); 17119 }; 17120 17121 17122 17123 /*< isSpace.js */ 17124 /* 17125 * isSpace.js - Character type is space char 17126 * 17127 * Copyright © 2012-2015, JEDLSoft 17128 * 17129 * Licensed under the Apache License, Version 2.0 (the "License"); 17130 * you may not use this file except in compliance with the License. 17131 * You may obtain a copy of the License at 17132 * 17133 * http://www.apache.org/licenses/LICENSE-2.0 17134 * 17135 * Unless required by applicable law or agreed to in writing, software 17136 * distributed under the License is distributed on an "AS IS" BASIS, 17137 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17138 * 17139 * See the License for the specific language governing permissions and 17140 * limitations under the License. 17141 */ 17142 17143 // !depends CType.js IString.js 17144 17145 // !data ctype ctype_z 17146 17147 17148 17149 /** 17150 * Return whether or not the first character is a whitespace character.<p> 17151 * 17152 * @static 17153 * @param {string|IString|number} ch character or code point to examine 17154 * @return {boolean} true if the first character is a whitespace character. 17155 */ 17156 var isSpace = function (ch) { 17157 var num; 17158 switch (typeof(ch)) { 17159 case 'number': 17160 num = ch; 17161 break; 17162 case 'string': 17163 num = IString.toCodePoint(ch, 0); 17164 break; 17165 case 'undefined': 17166 return false; 17167 default: 17168 num = ch._toCodePoint(0); 17169 break; 17170 } 17171 17172 return CType._inRange(num, 'space', ilib.data.ctype) || 17173 CType._inRange(num, 'Zs', ilib.data.ctype_z) || 17174 CType._inRange(num, 'Zl', ilib.data.ctype_z) || 17175 CType._inRange(num, 'Zp', ilib.data.ctype_z); 17176 }; 17177 17178 /** 17179 * @protected 17180 * @param {boolean} sync 17181 * @param {Object|undefined} loadParams 17182 * @param {function(*)|undefined} onLoad 17183 */ 17184 isSpace._init = function (sync, loadParams, onLoad) { 17185 CType._load("ctype_z", sync, loadParams, function () { 17186 CType._init(sync, loadParams, onLoad); 17187 }); 17188 }; 17189 17190 17191 /*< Currency.js */ 17192 /* 17193 * Currency.js - Currency definition 17194 * 17195 * Copyright © 2012-2015, JEDLSoft 17196 * 17197 * Licensed under the Apache License, Version 2.0 (the "License"); 17198 * you may not use this file except in compliance with the License. 17199 * You may obtain a copy of the License at 17200 * 17201 * http://www.apache.org/licenses/LICENSE-2.0 17202 * 17203 * Unless required by applicable law or agreed to in writing, software 17204 * distributed under the License is distributed on an "AS IS" BASIS, 17205 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17206 * 17207 * See the License for the specific language governing permissions and 17208 * limitations under the License. 17209 */ 17210 17211 // !depends ilib.js Utils.js Locale.js LocaleInfo.js 17212 17213 // !data currency 17214 17215 17216 /** 17217 * @class 17218 * Create a new currency information instance. Instances of this class encode 17219 * information about a particular currency.<p> 17220 * 17221 * Note: that if you are looking to format currency for display, please see 17222 * the number formatting class {NumFmt}. This class only gives information 17223 * about currencies.<p> 17224 * 17225 * The options can contain any of the following properties: 17226 * 17227 * <ul> 17228 * <li><i>locale</i> - specify the locale for this instance 17229 * <li><i>code</i> - find info on a specific currency with the given ISO 4217 code 17230 * <li><i>sign</i> - search for a currency that uses this sign 17231 * <li><i>onLoad</i> - a callback function to call when the currency data is fully 17232 * loaded. When the onLoad option is given, this class will attempt to 17233 * load any missing locale data using the ilib loader callback. 17234 * When the constructor is done (even if the data is already preassembled), the 17235 * onLoad function is called with the current instance as a parameter, so this 17236 * callback can be used with preassembled or dynamic loading or a mix of the two. 17237 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 17238 * asynchronously. If this option is given as "false", then the "onLoad" 17239 * callback must be given, as the instance returned from this constructor will 17240 * not be usable for a while. 17241 * <li><i>loadParams</i> - an object containing parameters to pass to the 17242 * loader callback function when locale data is missing. The parameters are not 17243 * interpretted or modified in any way. They are simply passed along. The object 17244 * may contain any property/value pairs as long as the calling code is in 17245 * agreement with the loader callback function as to what those parameters mean. 17246 * </ul> 17247 * 17248 * When searching for a currency by its sign, this class cannot guarantee 17249 * that it will return info about a specific currency. The reason is that currency 17250 * signs are sometimes shared between different currencies and the sign is 17251 * therefore ambiguous. If you need a 17252 * guarantee, find the currency using the code instead.<p> 17253 * 17254 * The way this class finds a currency by sign is the following. If the sign is 17255 * unambiguous, then 17256 * the currency is returned. If there are multiple currencies that use the same 17257 * sign, and the current locale uses that sign, then the default currency for 17258 * the current locale is returned. If there are multiple, but the current locale 17259 * does not use that sign, then the currency with the largest circulation is 17260 * returned. For example, if you are in the en-GB locale, and the sign is "$", 17261 * then this class will notice that there are multiple currencies with that 17262 * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will 17263 * pick the one with the largest circulation, which in this case is the US Dollar 17264 * (USD).<p> 17265 * 17266 * If neither the code or sign property is set, the currency that is most common 17267 * for the locale 17268 * will be used instead. If the locale is not set, the default locale will be used. 17269 * If the code is given, but it is not found in the list of known currencies, this 17270 * constructor will throw an exception. If the sign is given, but it is not found, 17271 * this constructor will default to the currency for the current locale. If both 17272 * the code and sign properties are given, then the sign property will be ignored 17273 * and only the code property used. If the locale is given, but it is not a known 17274 * locale, this class will default to the default locale instead.<p> 17275 * 17276 * 17277 * @constructor 17278 * @param options {Object} a set of properties to govern how this instance is constructed. 17279 * @throws "currency xxx is unknown" when the given currency code is not in the list of 17280 * known currencies. xxx is replaced with the requested code. 17281 */ 17282 var Currency = function (options) { 17283 this.sync = true; 17284 17285 if (options) { 17286 if (options.code) { 17287 this.code = options.code; 17288 } 17289 if (options.locale) { 17290 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17291 } 17292 if (options.sign) { 17293 this.sign = options.sign; 17294 } 17295 if (typeof(options.sync) !== 'undefined') { 17296 this.sync = options.sync; 17297 } 17298 if (options.loadParams) { 17299 this.loadParams = options.loadParams; 17300 } 17301 } 17302 17303 this.locale = this.locale || new Locale(); 17304 if (typeof(ilib.data.currency) === 'undefined') { 17305 Utils.loadData({ 17306 name: "currency.json", 17307 object: Currency, 17308 locale: "-", 17309 sync: this.sync, 17310 loadParams: this.loadParams, 17311 callback: ilib.bind(this, function(currency) { 17312 ilib.data.currency = currency; 17313 this._loadLocinfo(options && options.onLoad); 17314 }) 17315 }); 17316 } else { 17317 this._loadLocinfo(options && options.onLoad); 17318 } 17319 }; 17320 17321 /** 17322 * Return an array of the ids for all ISO 4217 currencies that 17323 * this copy of ilib knows about. 17324 * 17325 * @static 17326 * @return {Array.<string>} an array of currency ids that this copy of ilib knows about. 17327 */ 17328 Currency.getAvailableCurrencies = function() { 17329 var ret = [], 17330 cur, 17331 currencies = new ResBundle({ 17332 name: "currency" 17333 }).getResObj(); 17334 17335 for (cur in currencies) { 17336 if (cur && currencies[cur]) { 17337 ret.push(cur); 17338 } 17339 } 17340 17341 return ret; 17342 }; 17343 17344 Currency.prototype = { 17345 /** 17346 * @private 17347 */ 17348 _loadLocinfo: function(onLoad) { 17349 new LocaleInfo(this.locale, { 17350 onLoad: ilib.bind(this, function (li) { 17351 var currInfo; 17352 17353 this.locinfo = li; 17354 if (this.code) { 17355 currInfo = ilib.data.currency[this.code]; 17356 if (!currInfo) { 17357 throw "currency " + this.code + " is unknown"; 17358 } 17359 } else if (this.sign) { 17360 currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... 17361 if (typeof(currInfo) !== 'undefined') { 17362 this.code = this.sign; 17363 } else { 17364 this.code = this.locinfo.getCurrency(); 17365 currInfo = ilib.data.currency[this.code]; 17366 if (currInfo.sign !== this.sign) { 17367 // current locale does not use the sign, so search for it 17368 for (var cur in ilib.data.currency) { 17369 if (cur && ilib.data.currency[cur]) { 17370 currInfo = ilib.data.currency[cur]; 17371 if (currInfo.sign === this.sign) { 17372 // currency data is already ordered so that the currency with the 17373 // largest circulation is at the beginning, so all we have to do 17374 // is take the first one in the list that matches 17375 this.code = cur; 17376 break; 17377 } 17378 } 17379 } 17380 } 17381 } 17382 } 17383 17384 if (!currInfo || !this.code) { 17385 this.code = this.locinfo.getCurrency(); 17386 currInfo = ilib.data.currency[this.code]; 17387 } 17388 17389 this.name = currInfo.name; 17390 this.fractionDigits = currInfo.decimals; 17391 this.sign = currInfo.sign; 17392 17393 if (typeof(onLoad) === 'function') { 17394 onLoad(this); 17395 } 17396 }) 17397 }); 17398 }, 17399 17400 /** 17401 * Return the ISO 4217 currency code for this instance. 17402 * @return {string} the ISO 4217 currency code for this instance 17403 */ 17404 getCode: function () { 17405 return this.code; 17406 }, 17407 17408 /** 17409 * Return the default number of fraction digits that is typically used 17410 * with this type of currency. 17411 * @return {number} the number of fraction digits for this currency 17412 */ 17413 getFractionDigits: function () { 17414 return this.fractionDigits; 17415 }, 17416 17417 /** 17418 * Return the sign commonly used to represent this currency. 17419 * @return {string} the sign commonly used to represent this currency 17420 */ 17421 getSign: function () { 17422 return this.sign; 17423 }, 17424 17425 /** 17426 * Return the name of the currency in English. 17427 * @return {string} the name of the currency in English 17428 */ 17429 getName: function () { 17430 return this.name; 17431 }, 17432 17433 /** 17434 * Return the locale for this currency. If the options to the constructor 17435 * included a locale property in order to find the currency that is appropriate 17436 * for that locale, then the locale is returned here. If the options did not 17437 * include a locale, then this method returns undefined. 17438 * @return {Locale} the locale used in the constructor of this instance, 17439 * or undefined if no locale was given in the constructor 17440 */ 17441 getLocale: function () { 17442 return this.locale; 17443 } 17444 }; 17445 17446 17447 17448 /*< INumber.js */ 17449 /* 17450 * INumber.js - Parse a number in any locale 17451 * 17452 * Copyright © 2012-2015, JEDLSoft 17453 * 17454 * Licensed under the Apache License, Version 2.0 (the "License"); 17455 * you may not use this file except in compliance with the License. 17456 * You may obtain a copy of the License at 17457 * 17458 * http://www.apache.org/licenses/LICENSE-2.0 17459 * 17460 * Unless required by applicable law or agreed to in writing, software 17461 * distributed under the License is distributed on an "AS IS" BASIS, 17462 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17463 * 17464 * See the License for the specific language governing permissions and 17465 * limitations under the License. 17466 */ 17467 17468 /* 17469 !depends 17470 ilib.js 17471 Locale.js 17472 isDigit.js 17473 isSpace.js 17474 LocaleInfo.js 17475 Utils.js 17476 Currency.js 17477 */ 17478 17479 17480 17481 17482 17483 17484 /** 17485 * @class 17486 * Parse a string as a number, ignoring all locale-specific formatting.<p> 17487 * 17488 * This class is different from the standard Javascript parseInt() and parseFloat() 17489 * functions in that the number to be parsed can have formatting characters in it 17490 * that are not supported by those two 17491 * functions, and it handles numbers written in other locales properly. For example, 17492 * if you pass the string "203,231.23" to the parseFloat() function in Javascript, it 17493 * will return you the number 203. The INumber class will parse it correctly and 17494 * the value() function will return the number 203231.23. If you pass parseFloat() the 17495 * string "203.231,23" with the locale set to de-DE, it will return you 203 again. This 17496 * class will return the correct number 203231.23 again.<p> 17497 * 17498 * The options object may contain any of the following properties: 17499 * 17500 * <ul> 17501 * <li><i>locale</i> - specify the locale of the string to parse. This is used to 17502 * figure out what the decimal point character is. If not specified, the default locale 17503 * for the app or browser is used. 17504 * <li><i>type</i> - specify whether this string should be interpretted as a number, 17505 * currency, or percentage amount. When the number is interpretted as a currency 17506 * amount, the getCurrency() method will return something useful, otherwise it will 17507 * return undefined. If 17508 * the number is to be interpretted as percentage amount and there is a percentage sign 17509 * in the string, then the number will be returned 17510 * as a fraction from the valueOf() method. If there is no percentage sign, then the 17511 * number will be returned as a regular number. That is "58.3%" will be returned as the 17512 * number 0.583 but "58.3" will be returned as 58.3. Valid values for this property 17513 * are "number", "currency", and "percentage". Default if this is not specified is 17514 * "number". 17515 * <li><i>onLoad</i> - a callback function to call when the locale data is fully 17516 * loaded. When the onLoad option is given, this class will attempt to 17517 * load any missing locale data using the ilib loader callback. 17518 * When the constructor is done (even if the data is already preassembled), the 17519 * onLoad function is called with the current instance as a parameter, so this 17520 * callback can be used with preassembled or dynamic loading or a mix of the two. 17521 * 17522 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 17523 * asynchronously. If this option is given as "false", then the "onLoad" 17524 * callback must be given, as the instance returned from this constructor will 17525 * not be usable for a while. 17526 * 17527 * <li><i>loadParams</i> - an object containing parameters to pass to the 17528 * loader callback function when locale data is missing. The parameters are not 17529 * interpretted or modified in any way. They are simply passed along. The object 17530 * may contain any property/value pairs as long as the calling code is in 17531 * agreement with the loader callback function as to what those parameters mean. 17532 * </ul> 17533 * <p> 17534 * 17535 * This class is named INumber ("ilib number") so as not to conflict with the 17536 * built-in Javascript Number class. 17537 * 17538 * @constructor 17539 * @param {string|number|INumber|Number|undefined} str a string to parse as a number, or a number value 17540 * @param {Object=} options Options controlling how the instance should be created 17541 */ 17542 var INumber = function (str, options) { 17543 var i, stripped = "", 17544 sync = true, 17545 loadParams, 17546 onLoad; 17547 17548 this.locale = new Locale(); 17549 this.type = "number"; 17550 17551 if (options) { 17552 if (options.locale) { 17553 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17554 } 17555 if (options.type) { 17556 switch (options.type) { 17557 case "number": 17558 case "currency": 17559 case "percentage": 17560 this.type = options.type; 17561 break; 17562 default: 17563 break; 17564 } 17565 } 17566 if (typeof(options.sync) !== 'undefined') { 17567 sync = (options.sync == true); 17568 } 17569 loadParams = options.loadParams; 17570 onLoad = options.onLoad; 17571 } 17572 17573 isDigit._init(sync, loadParams, ilib.bind(this, function() { 17574 isSpace._init(sync, loadParams, ilib.bind(this, function() { 17575 new LocaleInfo(this.locale, { 17576 sync: sync, 17577 onLoad: ilib.bind(this, function (li) { 17578 this.decimal = li.getDecimalSeparator(); 17579 17580 switch (typeof(str)) { 17581 case 'string': 17582 // stripping should work for all locales, because you just ignore all the 17583 // formatting except the decimal char 17584 var unary = true; // looking for the unary minus still? 17585 var lastNumericChar = 0; 17586 this.str = str || "0"; 17587 i = 0; 17588 for (i = 0; i < this.str.length; i++) { 17589 if (unary && this.str.charAt(i) === '-') { 17590 unary = false; 17591 stripped += this.str.charAt(i); 17592 lastNumericChar = i; 17593 } else if (isDigit(this.str.charAt(i))) { 17594 stripped += this.str.charAt(i); 17595 unary = false; 17596 lastNumericChar = i; 17597 } else if (this.str.charAt(i) === this.decimal) { 17598 stripped += "."; // always convert to period 17599 unary = false; 17600 lastNumericChar = i; 17601 } // else ignore 17602 } 17603 // record what we actually parsed 17604 this.parsed = this.str.substring(0, lastNumericChar+1); 17605 /** @type {number} */ 17606 this.value = parseFloat(stripped); 17607 break; 17608 case 'number': 17609 this.str = "" + str; 17610 this.value = str; 17611 break; 17612 17613 case 'object': 17614 // call parseFloat to coerse the type to number 17615 this.value = parseFloat(str.valueOf()); 17616 this.str = "" + this.value; 17617 break; 17618 17619 case 'undefined': 17620 this.value = 0; 17621 this.str = "0"; 17622 break; 17623 } 17624 17625 switch (this.type) { 17626 default: 17627 // don't need to do anything special for other types 17628 break; 17629 case "percentage": 17630 if (this.str.indexOf(li.getPercentageSymbol()) !== -1) { 17631 this.value /= 100; 17632 } 17633 break; 17634 case "currency": 17635 stripped = ""; 17636 i = 0; 17637 while (i < this.str.length && 17638 !isDigit(this.str.charAt(i)) && 17639 !isSpace(this.str.charAt(i))) { 17640 stripped += this.str.charAt(i++); 17641 } 17642 if (stripped.length === 0) { 17643 while (i < this.str.length && 17644 isDigit(this.str.charAt(i)) || 17645 isSpace(this.str.charAt(i)) || 17646 this.str.charAt(i) === '.' || 17647 this.str.charAt(i) === ',' ) { 17648 i++; 17649 } 17650 while (i < this.str.length && 17651 !isDigit(this.str.charAt(i)) && 17652 !isSpace(this.str.charAt(i))) { 17653 stripped += this.str.charAt(i++); 17654 } 17655 } 17656 new Currency({ 17657 locale: this.locale, 17658 sign: stripped, 17659 sync: sync, 17660 onLoad: ilib.bind(this, function (cur) { 17661 this.currency = cur; 17662 if (options && typeof(options.onLoad) === 'function') { 17663 options.onLoad(this); 17664 } 17665 }) 17666 }); 17667 return; 17668 } 17669 17670 if (options && typeof(options.onLoad) === 'function') { 17671 options.onLoad(this); 17672 } 17673 }) 17674 }); 17675 })); 17676 })); 17677 }; 17678 17679 INumber.prototype = { 17680 /** 17681 * Return the locale for this formatter instance. 17682 * @return {Locale} the locale instance for this formatter 17683 */ 17684 getLocale: function () { 17685 return this.locale; 17686 }, 17687 17688 /** 17689 * Return the original string that this number instance was created with. 17690 * @return {string} the original string 17691 */ 17692 toString: function () { 17693 return this.str; 17694 }, 17695 17696 /** 17697 * If the type of this INumber instance is "currency", then the parser will attempt 17698 * to figure out which currency this amount represents. The amount can be written 17699 * with any of the currency signs or ISO 4217 codes that are currently 17700 * recognized by ilib, and the currency signs may occur before or after the 17701 * numeric portion of the string. If no currency can be recognized, then the 17702 * default currency for the locale is returned. If multiple currencies can be 17703 * recognized (for example if the currency sign is "$"), then this method 17704 * will prefer the one for the current locale. If multiple currencies can be 17705 * recognized, but none are used in the current locale, then the first currency 17706 * encountered will be used. This may produce random results, though the larger 17707 * currencies occur earlier in the list. For example, if the sign found in the 17708 * string is "$" and that is not the sign of the currency of the current locale 17709 * then the US dollar will be recognized, as it is the largest currency that uses 17710 * the "$" as its sign. 17711 * 17712 * @return {Currency|undefined} the currency instance for this amount, or 17713 * undefined if this INumber object is not of type currency 17714 */ 17715 getCurrency: function () { 17716 return this.currency; 17717 }, 17718 17719 /** 17720 * Return the value of this INumber object as a primitive number instance. 17721 * @return {number} the value of this number instance 17722 */ 17723 valueOf: function () { 17724 return this.value; 17725 } 17726 }; 17727 17728 17729 /*< NumFmt.js */ 17730 /* 17731 * NumFmt.js - Number formatter definition 17732 * 17733 * Copyright © 2012-2015, JEDLSoft 17734 * 17735 * Licensed under the Apache License, Version 2.0 (the "License"); 17736 * you may not use this file except in compliance with the License. 17737 * You may obtain a copy of the License at 17738 * 17739 * http://www.apache.org/licenses/LICENSE-2.0 17740 * 17741 * Unless required by applicable law or agreed to in writing, software 17742 * distributed under the License is distributed on an "AS IS" BASIS, 17743 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17744 * 17745 * See the License for the specific language governing permissions and 17746 * limitations under the License. 17747 */ 17748 17749 /* 17750 !depends 17751 ilib.js 17752 Locale.js 17753 LocaleInfo.js 17754 Utils.js 17755 MathUtils.js 17756 Currency.js 17757 IString.js 17758 JSUtils.js 17759 INumber.js 17760 */ 17761 17762 // !data localeinfo currency 17763 17764 17765 17766 /** 17767 * @class 17768 * Create a new number formatter instance. Locales differ in the way that digits 17769 * in a formatted number are grouped, in the way the decimal character is represented, 17770 * etc. Use this formatter to get it right for any locale.<p> 17771 * 17772 * This formatter can format plain numbers, currency amounts, and percentage amounts.<p> 17773 * 17774 * As with all formatters, the recommended 17775 * practice is to create one formatter and use it multiple times to format various 17776 * numbers.<p> 17777 * 17778 * The options can contain any of the following properties: 17779 * 17780 * <ul> 17781 * <li><i>locale</i> - use the conventions of the specified locale when figuring out how to 17782 * format a number. 17783 * <li><i>type</i> - the type of this formatter. Valid values are "number", "currency", or 17784 * "percentage". If this property is not specified, the default is "number". 17785 * <li><i>currency</i> - the ISO 4217 3-letter currency code to use when the formatter type 17786 * is "currency". This property is required for currency formatting. If the type property 17787 * is "currency" and the currency property is not specified, the constructor will throw a 17788 * an exception. 17789 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 17790 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 17791 * the integral part of the number. 17792 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 17793 * appear in the formatted output. If the number does not have enough fractional digits 17794 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 17795 * If the type of the formatter is "currency" and this 17796 * property is not specified, then the minimum fraction digits is set to the normal number 17797 * of digits used with that currency, which is almost always 0, 2, or 3 digits. 17798 * <li><i>useNative</i> - the flag used to determaine whether to use the native script settings 17799 * for formatting the numbers . 17800 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 17801 * this property governs how the least significant digits are rounded to conform to that 17802 * maximum. The value of this property is a string with one of the following values: 17803 * <ul> 17804 * <li><i>up</i> - round away from zero 17805 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 17806 * <li><i>ceiling</i> - round towards positive infinity 17807 * <li><i>floor</i> - round towards negative infinity 17808 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 17809 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 17810 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 17811 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 17812 * </ul> 17813 * When the type of the formatter is "currency" and the <i>roundingMode</i> property is not 17814 * set, then the standard legal rounding rules for the locale are followed. If the type 17815 * is "number" or "percentage" and the <i>roundingMode</i> property is not set, then the 17816 * default mode is "halfdown".</i>. 17817 * 17818 * <li><i>style</i> - When the type of this formatter is "currency", the currency amount 17819 * can be formatted in the following styles: "common" and "iso". The common style is the 17820 * one commonly used in every day writing where the currency unit is represented using a 17821 * symbol. eg. "$57.35" for fifty-seven dollars and thirty five cents. The iso style is 17822 * the international style where the currency unit is represented using the ISO 4217 code. 17823 * eg. "USD 57.35" for the same amount. The default is "common" style if the style is 17824 * not specified.<p> 17825 * 17826 * When the type of this formatter is "number", the style can be one of the following: 17827 * <ul> 17828 * <li><i>standard - format a fully specified floating point number properly for the locale 17829 * <li><i>scientific</i> - use scientific notation for all numbers. That is, 1 integral 17830 * digit, followed by a number of fractional digits, followed by an "e" which denotes 17831 * exponentiation, followed digits which give the power of 10 in the exponent. 17832 * <li><i>native</i> - format a floating point number using the native digits and 17833 * formatting symbols for the script of the locale. 17834 * <li><i>nogrouping</i> - format a floating point number without grouping digits for 17835 * the integral portion of the number 17836 * </ul> 17837 * Note that if you specify a maximum number 17838 * of integral digits, the formatter with a standard style will give you standard 17839 * formatting for smaller numbers and scientific notation for larger numbers. The default 17840 * is standard style if this is not specified. 17841 * 17842 * <li><i>onLoad</i> - a callback function to call when the format data is fully 17843 * loaded. When the onLoad option is given, this class will attempt to 17844 * load any missing locale data using the ilib loader callback. 17845 * When the constructor is done (even if the data is already preassembled), the 17846 * onLoad function is called with the current instance as a parameter, so this 17847 * callback can be used with preassembled or dynamic loading or a mix of the two. 17848 * 17849 * <li>sync - tell whether to load any missing locale data synchronously or 17850 * asynchronously. If this option is given as "false", then the "onLoad" 17851 * callback must be given, as the instance returned from this constructor will 17852 * not be usable for a while. 17853 * 17854 * <li><i>loadParams</i> - an object containing parameters to pass to the 17855 * loader callback function when locale data is missing. The parameters are not 17856 * interpretted or modified in any way. They are simply passed along. The object 17857 * may contain any property/value pairs as long as the calling code is in 17858 * agreement with the loader callback function as to what those parameters mean. 17859 * </ul> 17860 * <p> 17861 * 17862 * 17863 * @constructor 17864 * @param {Object.<string,*>} options A set of options that govern how the formatter will behave 17865 */ 17866 var NumFmt = function (options) { 17867 var sync = true; 17868 this.locale = new Locale(); 17869 /** 17870 * @private 17871 * @type {string} 17872 */ 17873 this.type = "number"; 17874 var loadParams = undefined; 17875 17876 if (options) { 17877 if (options.locale) { 17878 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17879 } 17880 17881 if (options.type) { 17882 if (options.type === 'number' || 17883 options.type === 'currency' || 17884 options.type === 'percentage') { 17885 this.type = options.type; 17886 } 17887 } 17888 17889 if (options.currency) { 17890 /** 17891 * @private 17892 * @type {string} 17893 */ 17894 this.currency = options.currency; 17895 } 17896 17897 if (typeof (options.maxFractionDigits) === 'number') { 17898 /** 17899 * @private 17900 * @type {number|undefined} 17901 */ 17902 this.maxFractionDigits = this._toPrimitive(options.maxFractionDigits); 17903 } 17904 if (typeof (options.minFractionDigits) === 'number') { 17905 /** 17906 * @private 17907 * @type {number|undefined} 17908 */ 17909 this.minFractionDigits = this._toPrimitive(options.minFractionDigits); 17910 // enforce the limits to avoid JS exceptions 17911 if (this.minFractionDigits < 0) { 17912 this.minFractionDigits = 0; 17913 } 17914 if (this.minFractionDigits > 20) { 17915 this.minFractionDigits = 20; 17916 } 17917 } 17918 if (options.style) { 17919 /** 17920 * @private 17921 * @type {string} 17922 */ 17923 this.style = options.style; 17924 } 17925 if (typeof(options.useNative) === 'boolean') { 17926 /** 17927 * @private 17928 * @type {boolean} 17929 * */ 17930 this.useNative = options.useNative; 17931 } 17932 /** 17933 * @private 17934 * @type {string} 17935 */ 17936 this.roundingMode = options.roundingMode; 17937 17938 if (typeof(options.sync) === 'boolean') { 17939 sync = options.sync; 17940 } 17941 17942 loadParams = options.loadParams; 17943 } 17944 17945 /** 17946 * @private 17947 * @type {LocaleInfo|undefined} 17948 */ 17949 this.localeInfo = undefined; 17950 17951 new LocaleInfo(this.locale, { 17952 sync: sync, 17953 loadParams: loadParams, 17954 onLoad: ilib.bind(this, function (li) { 17955 /** 17956 * @private 17957 * @type {LocaleInfo|undefined} 17958 */ 17959 this.localeInfo = li; 17960 17961 if (this.type === "number") { 17962 this.templateNegative = new IString(this.localeInfo.getNegativeNumberFormat() || "-{n}"); 17963 } else if (this.type === "currency") { 17964 var templates; 17965 17966 if (!this.currency || typeof (this.currency) != 'string') { 17967 throw "A currency property is required in the options to the number formatter constructor when the type property is set to currency."; 17968 } 17969 17970 new Currency({ 17971 locale: this.locale, 17972 code: this.currency, 17973 sync: sync, 17974 loadParams: loadParams, 17975 onLoad: ilib.bind(this, function (cur) { 17976 this.currencyInfo = cur; 17977 if (this.style !== "common" && this.style !== "iso") { 17978 this.style = "common"; 17979 } 17980 17981 if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.minFractionDigits) !== 'number') { 17982 this.minFractionDigits = this.maxFractionDigits = this.currencyInfo.getFractionDigits(); 17983 } 17984 17985 templates = this.localeInfo.getCurrencyFormats(); 17986 this.template = new IString(templates[this.style] || templates.common); 17987 this.templateNegative = new IString(templates[this.style + "Negative"] || templates["commonNegative"]); 17988 this.sign = (this.style === "iso") ? this.currencyInfo.getCode() : this.currencyInfo.getSign(); 17989 17990 if (!this.roundingMode) { 17991 this.roundingMode = this.currencyInfo && this.currencyInfo.roundingMode; 17992 } 17993 17994 this._init(); 17995 17996 if (options && typeof (options.onLoad) === 'function') { 17997 options.onLoad(this); 17998 } 17999 }) 18000 }); 18001 return; 18002 } else if (this.type === "percentage") { 18003 this.template = new IString(this.localeInfo.getPercentageFormat() || "{n}%"); 18004 this.templateNegative = new IString(this.localeInfo.getNegativePercentageFormat() || this.localeInfo.getNegativeNumberFormat() + "%"); 18005 } 18006 18007 this._init(); 18008 18009 if (options && typeof (options.onLoad) === 'function') { 18010 options.onLoad(this); 18011 } 18012 }) 18013 }); 18014 }; 18015 18016 /** 18017 * Return an array of available locales that this formatter can format 18018 * @static 18019 * @return {Array.<Locale>|undefined} an array of available locales 18020 */ 18021 NumFmt.getAvailableLocales = function () { 18022 return undefined; 18023 }; 18024 18025 /** 18026 * @private 18027 * @const 18028 * @type string 18029 */ 18030 NumFmt.zeros = "0000000000000000000000000000000000000000000000000000000000000000000000"; 18031 18032 NumFmt.prototype = { 18033 /** 18034 * Return true if this formatter uses native digits to format the number. If the useNative 18035 * option is given to the constructor, then this flag will be honoured. If the useNative 18036 * option is not given to the constructor, this this formatter will use native digits if 18037 * the locale typically uses native digits. 18038 * 18039 * @return {boolean} true if this formatter will format with native digits, false otherwise 18040 */ 18041 getUseNative: function() { 18042 if (typeof(this.useNative) === "boolean") { 18043 return this.useNative; 18044 } 18045 return (this.localeInfo.getDigitsStyle() === "native"); 18046 }, 18047 18048 /** 18049 * @private 18050 */ 18051 _init: function () { 18052 if (this.maxFractionDigits < this.minFractionDigits) { 18053 this.minFractionDigits = this.maxFractionDigits; 18054 } 18055 18056 if (!this.roundingMode) { 18057 this.roundingMode = this.localeInfo.getRoundingMode(); 18058 } 18059 18060 if (!this.roundingMode) { 18061 this.roundingMode = "halfdown"; 18062 } 18063 18064 // set up the function, so we only have to figure it out once 18065 // and not every time we do format() 18066 this.round = MathUtils[this.roundingMode]; 18067 if (!this.round) { 18068 this.roundingMode = "halfdown"; 18069 this.round = MathUtils[this.roundingMode]; 18070 } 18071 18072 if (this.style === "nogrouping") { 18073 this.prigroupSize = this.secgroupSize = 0; 18074 } else { 18075 this.prigroupSize = this.localeInfo.getPrimaryGroupingDigits(); 18076 this.secgroupSize = this.localeInfo.getSecondaryGroupingDigits(); 18077 this.groupingSeparator = this.getUseNative() ? this.localeInfo.getNativeGroupingSeparator() : this.localeInfo.getGroupingSeparator(); 18078 } 18079 this.decimalSeparator = this.getUseNative() ? this.localeInfo.getNativeDecimalSeparator() : this.localeInfo.getDecimalSeparator(); 18080 18081 if (this.getUseNative()) { 18082 var nd = this.localeInfo.getNativeDigits() || this.localeInfo.getDigits(); 18083 if (nd) { 18084 this.digits = nd.split(""); 18085 } 18086 } 18087 18088 this.exponentSymbol = this.localeInfo.getExponential() || "e"; 18089 }, 18090 18091 /** 18092 * @private 18093 * @param {INumber|Number|string|number} num object, string, or number to convert to a primitive number 18094 * @return {number} the primitive number equivalent of the argument 18095 */ 18096 _toPrimitive: function (num) { 18097 var n = 0; 18098 18099 switch (typeof (num)) { 18100 case 'number': 18101 n = num; 18102 break; 18103 case 'string': 18104 n = parseFloat(num); 18105 break; 18106 case 'object': 18107 // call parseFloat to coerse the type to number 18108 n = parseFloat(num.valueOf()); 18109 break; 18110 } 18111 18112 return n; 18113 }, 18114 18115 /** 18116 * Format the number using scientific notation as a positive number. Negative 18117 * formatting to be applied later. 18118 * @private 18119 * @param {number} num the number to format 18120 * @return {string} the formatted number 18121 */ 18122 _formatScientific: function (num) { 18123 var n = new Number(num); 18124 var formatted; 18125 18126 var factor, 18127 str = n.toExponential(), 18128 parts = str.split("e"), 18129 significant = parts[0], 18130 exponent = parts[1], 18131 numparts, 18132 integral, 18133 fraction; 18134 18135 if (this.maxFractionDigits > 0) { 18136 // if there is a max fraction digits setting, round the fraction to 18137 // the right length first by dividing or multiplying by powers of 10. 18138 // manipulate the fraction digits so as to 18139 // avoid the rounding errors of floating point numbers 18140 factor = Math.pow(10, this.maxFractionDigits); 18141 significant = this.round(significant * factor) / factor; 18142 } 18143 numparts = ("" + significant).split("."); 18144 integral = numparts[0]; 18145 fraction = numparts[1]; 18146 18147 if (typeof(this.maxFractionDigits) !== 'undefined') { 18148 fraction = fraction.substring(0, this.maxFractionDigits); 18149 } 18150 if (typeof(this.minFractionDigits) !== 'undefined') { 18151 fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); 18152 } 18153 formatted = integral; 18154 if (fraction.length) { 18155 formatted += this.decimalSeparator + fraction; 18156 } 18157 formatted += this.exponentSymbol + exponent; 18158 return formatted; 18159 }, 18160 18161 /** 18162 * Formats the number as a positive number. Negative formatting to be applied later. 18163 * @private 18164 * @param {number} num the number to format 18165 * @return {string} the formatted number 18166 */ 18167 _formatStandard: function (num) { 18168 var i; 18169 var k; 18170 18171 if (typeof(this.maxFractionDigits) !== 'undefined' && this.maxFractionDigits > -1) { 18172 var factor = Math.pow(10, this.maxFractionDigits); 18173 num = this.round(num * factor) / factor; 18174 } 18175 18176 num = Math.abs(num); 18177 18178 var parts = ("" + num).split("."), 18179 integral = parts[0], 18180 fraction = parts[1], 18181 cycle, 18182 formatted; 18183 18184 integral = integral.toString(); 18185 18186 if (this.minFractionDigits > 0) { 18187 fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); 18188 } 18189 18190 if (this.secgroupSize > 0) { 18191 if (integral.length > this.prigroupSize) { 18192 var size1 = this.prigroupSize; 18193 var size2 = integral.length; 18194 var size3 = size2 - size1; 18195 integral = integral.slice(0, size3) + this.groupingSeparator + integral.slice(size3); 18196 var num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 18197 k = num_sec.length; 18198 while (k > this.secgroupSize) { 18199 var secsize1 = this.secgroupSize; 18200 var secsize2 = num_sec.length; 18201 var secsize3 = secsize2 - secsize1; 18202 integral = integral.slice(0, secsize3) + this.groupingSeparator + integral.slice(secsize3); 18203 num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 18204 k = num_sec.length; 18205 } 18206 } 18207 18208 formatted = integral; 18209 } else if (this.prigroupSize !== 0) { 18210 cycle = MathUtils.mod(integral.length - 1, this.prigroupSize); 18211 18212 formatted = ""; 18213 18214 for (i = 0; i < integral.length - 1; i++) { 18215 formatted += integral.charAt(i); 18216 if (cycle === 0) { 18217 formatted += this.groupingSeparator; 18218 } 18219 cycle = MathUtils.mod(cycle - 1, this.prigroupSize); 18220 } 18221 formatted += integral.charAt(integral.length - 1); 18222 } else { 18223 formatted = integral; 18224 } 18225 18226 if (fraction && (typeof(this.maxFractionDigits) === 'undefined' || this.maxFractionDigits > 0)) { 18227 formatted += this.decimalSeparator; 18228 formatted += fraction; 18229 } 18230 18231 if (this.digits) { 18232 formatted = JSUtils.mapString(formatted, this.digits); 18233 } 18234 18235 return formatted; 18236 }, 18237 18238 /** 18239 * Format a number according to the settings of this number formatter instance. 18240 * @param num {number|string|INumber|Number} a floating point number to format 18241 * @return {string} a string containing the formatted number 18242 */ 18243 format: function (num) { 18244 var formatted, n; 18245 18246 if (typeof (num) === 'undefined') { 18247 return ""; 18248 } 18249 18250 // convert to a real primitive number type 18251 n = this._toPrimitive(num); 18252 18253 if (this.type === "number") { 18254 formatted = (this.style === "scientific") ? 18255 this._formatScientific(n) : 18256 this._formatStandard(n); 18257 18258 if (num < 0) { 18259 formatted = this.templateNegative.format({n: formatted}); 18260 } 18261 } else { 18262 formatted = this._formatStandard(n); 18263 var template = (n < 0) ? this.templateNegative : this.template; 18264 formatted = template.format({ 18265 n: formatted, 18266 s: this.sign 18267 }); 18268 } 18269 18270 return formatted; 18271 }, 18272 18273 /** 18274 * Return the type of formatter. Valid values are "number", "currency", and 18275 * "percentage". 18276 * 18277 * @return {string} the type of formatter 18278 */ 18279 getType: function () { 18280 return this.type; 18281 }, 18282 18283 /** 18284 * Return the locale for this formatter instance. 18285 * @return {Locale} the locale instance for this formatter 18286 */ 18287 getLocale: function () { 18288 return this.locale; 18289 }, 18290 18291 /** 18292 * Returns true if this formatter groups together digits in the integral 18293 * portion of a number, based on the options set up in the constructor. In 18294 * most western European cultures, this means separating every 3 digits 18295 * of the integral portion of a number with a particular character. 18296 * 18297 * @return {boolean} true if this formatter groups digits in the integral 18298 * portion of the number 18299 */ 18300 isGroupingUsed: function () { 18301 return (this.groupingSeparator !== 'undefined' && this.groupingSeparator.length > 0); 18302 }, 18303 18304 /** 18305 * Returns the maximum fraction digits set up in the constructor. 18306 * 18307 * @return {number} the maximum number of fractional digits this 18308 * formatter will format, or -1 for no maximum 18309 */ 18310 getMaxFractionDigits: function () { 18311 return typeof (this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : -1; 18312 }, 18313 18314 /** 18315 * Returns the minimum fraction digits set up in the constructor. If 18316 * the formatter has the type "currency", then the minimum fraction 18317 * digits is the amount of digits that is standard for the currency 18318 * in question unless overridden in the options to the constructor. 18319 * 18320 * @return {number} the minimum number of fractional digits this 18321 * formatter will format, or -1 for no minimum 18322 */ 18323 getMinFractionDigits: function () { 18324 return typeof (this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : -1; 18325 }, 18326 18327 /** 18328 * Returns the ISO 4217 code for the currency that this formatter formats. 18329 * IF the typeof this formatter is not "currency", then this method will 18330 * return undefined. 18331 * 18332 * @return {string} the ISO 4217 code for the currency that this formatter 18333 * formats, or undefined if this not a currency formatter 18334 */ 18335 getCurrency: function () { 18336 return this.currencyInfo && this.currencyInfo.getCode(); 18337 }, 18338 18339 /** 18340 * Returns the rounding mode set up in the constructor. The rounding mode 18341 * controls how numbers are rounded when the integral or fraction digits 18342 * of a number are limited. 18343 * 18344 * @return {string} the name of the rounding mode used in this formatter 18345 */ 18346 getRoundingMode: function () { 18347 return this.roundingMode; 18348 }, 18349 18350 /** 18351 * If this formatter is a currency formatter, then the style determines how the 18352 * currency is denoted in the formatted output. This method returns the style 18353 * that this formatter will produce. (See the constructor comment for more about 18354 * the styles.) 18355 * @return {string} the name of the style this formatter will use to format 18356 * currency amounts, or "undefined" if this formatter is not a currency formatter 18357 */ 18358 getStyle: function () { 18359 return this.style; 18360 } 18361 }; 18362 18363 18364 /*< DurationFmt.js */ 18365 /* 18366 * DurFmt.js - Date formatter definition 18367 * 18368 * Copyright © 2012-2015, JEDLSoft 18369 * 18370 * Licensed under the Apache License, Version 2.0 (the "License"); 18371 * you may not use this file except in compliance with the License. 18372 * You may obtain a copy of the License at 18373 * 18374 * http://www.apache.org/licenses/LICENSE-2.0 18375 * 18376 * Unless required by applicable law or agreed to in writing, software 18377 * distributed under the License is distributed on an "AS IS" BASIS, 18378 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18379 * 18380 * See the License for the specific language governing permissions and 18381 * limitations under the License. 18382 */ 18383 18384 /* 18385 !depends 18386 ilib.js 18387 Locale.js 18388 DateFmt.js 18389 IString.js 18390 ResBundle.js 18391 LocaleInfo.js 18392 JSUtils.js 18393 Utils.js 18394 */ 18395 18396 // !data dateformats sysres 18397 // !resbundle sysres 18398 18399 18400 /** 18401 * @class 18402 * Create a new duration formatter instance. The duration formatter is immutable once 18403 * it is created, but can format as many different durations as needed with the same 18404 * options. Create different duration formatter instances for different purposes 18405 * and then keep them cached for use later if you have more than one duration to 18406 * format.<p> 18407 * 18408 * Duration formatters format lengths of time. The duration formatter is meant to format 18409 * durations of such things as the length of a song or a movie or a meeting, or the 18410 * current position in that song or movie while playing it. If you wish to format a 18411 * period of time that has a specific start and end date/time, then use a 18412 * [DateRngFmt] instance instead and call its format method.<p> 18413 * 18414 * The options may contain any of the following properties: 18415 * 18416 * <ul> 18417 * <li><i>locale</i> - locale to use when formatting the duration. If the locale is 18418 * not specified, then the default locale of the app or web page will be used. 18419 * 18420 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 18421 * formatted string. 18422 * 18423 * <ul> 18424 * <li><i>short</i> - use a short representation of the duration. This is the most compact format possible for the locale. eg. 1y 1m 1w 1d 1:01:01 18425 * <li><i>medium</i> - use a medium length representation of the duration. This is a slightly longer format. eg. 1 yr 1 mo 1 wk 1 dy 1 hr 1 mi 1 se 18426 * <li><i>long</i> - use a long representation of the duration. This is a fully specified format, but some of the textual 18427 * parts may still be abbreviated. eg. 1 yr 1 mo 1 wk 1 day 1 hr 1 min 1 sec 18428 * <li><i>full</i> - use a full representation of the duration. This is a fully specified format where all the textual 18429 * parts are spelled out completely. eg. 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute and 1 second 18430 * </ul> 18431 * 18432 * <li><i>style<i> - whether hours, minutes, and seconds should be formatted as a text string 18433 * or as a regular time as on a clock. eg. text is "1 hour, 15 minutes", whereas clock is "1:15:00". Valid 18434 * values for this property are "text" or "clock". Default if this property is not specified 18435 * is "text". 18436 * 18437 *<li><i>useNative</i> - the flag used to determaine whether to use the native script settings 18438 * for formatting the numbers . 18439 * 18440 * <li><i>onLoad</i> - a callback function to call when the format data is fully 18441 * loaded. When the onLoad option is given, this class will attempt to 18442 * load any missing locale data using the ilib loader callback. 18443 * When the constructor is done (even if the data is already preassembled), the 18444 * onLoad function is called with the current instance as a parameter, so this 18445 * callback can be used with preassembled or dynamic loading or a mix of the two. 18446 * 18447 * <li>sync - tell whether to load any missing locale data synchronously or 18448 * asynchronously. If this option is given as "false", then the "onLoad" 18449 * callback must be given, as the instance returned from this constructor will 18450 * not be usable for a while. 18451 * 18452 * <li><i>loadParams</i> - an object containing parameters to pass to the 18453 * loader callback function when locale data is missing. The parameters are not 18454 * interpretted or modified in any way. They are simply passed along. The object 18455 * may contain any property/value pairs as long as the calling code is in 18456 * agreement with the loader callback function as to what those parameters mean. 18457 * </ul> 18458 * <p> 18459 * 18460 * 18461 * @constructor 18462 * @param {?Object} options options governing the way this date formatter instance works 18463 */ 18464 var DurationFmt = function(options) { 18465 var sync = true; 18466 var loadParams = undefined; 18467 18468 this.locale = new Locale(); 18469 this.length = "short"; 18470 this.style = "text"; 18471 18472 if (options) { 18473 if (options.locale) { 18474 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 18475 } 18476 18477 if (options.length) { 18478 if (options.length === 'short' || 18479 options.length === 'medium' || 18480 options.length === 'long' || 18481 options.length === 'full') { 18482 this.length = options.length; 18483 } 18484 } 18485 18486 if (options.style) { 18487 if (options.style === 'text' || options.style === 'clock') { 18488 this.style = options.style; 18489 } 18490 } 18491 18492 if (typeof(options.sync) !== 'undefined') { 18493 sync = (options.sync == true); 18494 } 18495 18496 if (typeof(options.useNative) === 'boolean') { 18497 this.useNative = options.useNative; 18498 } 18499 18500 loadParams = options.loadParams; 18501 } 18502 18503 new ResBundle({ 18504 locale: this.locale, 18505 name: "sysres", 18506 sync: sync, 18507 loadParams: loadParams, 18508 onLoad: ilib.bind(this, function (sysres) { 18509 switch (this.length) { 18510 case 'short': 18511 this.components = { 18512 year: sysres.getString("#{num}y"), 18513 month: sysres.getString("#{num}m", "durationShortMonths"), 18514 week: sysres.getString("#{num}w"), 18515 day: sysres.getString("#{num}d"), 18516 hour: sysres.getString("#{num}h"), 18517 minute: sysres.getString("#{num}m", "durationShortMinutes"), 18518 second: sysres.getString("#{num}s"), 18519 millisecond: sysres.getString("#{num}m", "durationShortMillis"), 18520 separator: sysres.getString(" ", "separatorShort"), 18521 finalSeparator: "" // not used at this length 18522 }; 18523 break; 18524 18525 case 'medium': 18526 this.components = { 18527 year: sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"), 18528 month: sysres.getString("1#1 mo|#{num} mos"), 18529 week: sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"), 18530 day: sysres.getString("1#1 dy|#{num} dys"), 18531 hour: sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"), 18532 minute: sysres.getString("1#1 mi|#{num} min"), 18533 second: sysres.getString("1#1 se|#{num} sec"), 18534 millisecond: sysres.getString("#{num} ms"), 18535 separator: sysres.getString(" ", "separatorMedium"), 18536 finalSeparator: "" // not used at this length 18537 }; 18538 break; 18539 18540 case 'long': 18541 this.components = { 18542 year: sysres.getString("1#1 yr|#{num} yrs"), 18543 month: sysres.getString("1#1 mon|#{num} mons"), 18544 week: sysres.getString("1#1 wk|#{num} wks"), 18545 day: sysres.getString("1#1 day|#{num} days", "durationLongDays"), 18546 hour: sysres.getString("1#1 hr|#{num} hrs"), 18547 minute: sysres.getString("1#1 min|#{num} min"), 18548 second: sysres.getString("1#1 sec|#{num} sec"), 18549 millisecond: sysres.getString("#{num} ms"), 18550 separator: sysres.getString(", ", "separatorLong"), 18551 finalSeparator: "" // not used at this length 18552 }; 18553 break; 18554 18555 case 'full': 18556 this.components = { 18557 year: sysres.getString("1#1 year|#{num} years"), 18558 month: sysres.getString("1#1 month|#{num} months"), 18559 week: sysres.getString("1#1 week|#{num} weeks"), 18560 day: sysres.getString("1#1 day|#{num} days"), 18561 hour: sysres.getString("1#1 hour|#{num} hours"), 18562 minute: sysres.getString("1#1 minute|#{num} minutes"), 18563 second: sysres.getString("1#1 second|#{num} seconds"), 18564 millisecond: sysres.getString("1#1 millisecond|#{num} milliseconds"), 18565 separator: sysres.getString(", ", "separatorFull"), 18566 finalSeparator: sysres.getString(" and ", "finalSeparatorFull") 18567 }; 18568 break; 18569 } 18570 18571 if (this.style === 'clock') { 18572 new DateFmt({ 18573 locale: this.locale, 18574 calendar: "gregorian", 18575 type: "time", 18576 time: "ms", 18577 sync: sync, 18578 loadParams: loadParams, 18579 useNative: this.useNative, 18580 onLoad: ilib.bind(this, function (fmtMS) { 18581 this.timeFmtMS = fmtMS; 18582 new DateFmt({ 18583 locale: this.locale, 18584 calendar: "gregorian", 18585 type: "time", 18586 time: "hm", 18587 sync: sync, 18588 loadParams: loadParams, 18589 useNative: this.useNative, 18590 onLoad: ilib.bind(this, function (fmtHM) { 18591 this.timeFmtHM = fmtHM; 18592 new DateFmt({ 18593 locale: this.locale, 18594 calendar: "gregorian", 18595 type: "time", 18596 time: "hms", 18597 sync: sync, 18598 loadParams: loadParams, 18599 useNative: this.useNative, 18600 onLoad: ilib.bind(this, function (fmtHMS) { 18601 this.timeFmtHMS = fmtHMS; 18602 18603 // munge with the template to make sure that the hours are not formatted mod 12 18604 this.timeFmtHM.template = this.timeFmtHM.template.replace(/hh?/, 'H'); 18605 this.timeFmtHM.templateArr = this.timeFmtHM._tokenize(this.timeFmtHM.template); 18606 this.timeFmtHMS.template = this.timeFmtHMS.template.replace(/hh?/, 'H'); 18607 this.timeFmtHMS.templateArr = this.timeFmtHMS._tokenize(this.timeFmtHMS.template); 18608 18609 this._init(this.timeFmtHM.locinfo, options && options.onLoad); 18610 }) 18611 }); 18612 }) 18613 }); 18614 }) 18615 }); 18616 return; 18617 } 18618 18619 new LocaleInfo(this.locale, { 18620 sync: sync, 18621 loadParams: loadParams, 18622 onLoad: ilib.bind(this, function (li) { 18623 this._init(li, options && options.onLoad); 18624 }) 18625 }); 18626 }) 18627 }); 18628 }; 18629 18630 /** 18631 * @private 18632 * @static 18633 */ 18634 DurationFmt.complist = { 18635 "text": ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"], 18636 "clock": ["year", "month", "week", "day"] 18637 }; 18638 18639 /** 18640 * @private 18641 */ 18642 DurationFmt.prototype._mapDigits = function(str) { 18643 if (this.useNative && this.digits) { 18644 return JSUtils.mapString(str.toString(), this.digits); 18645 } 18646 return str; 18647 }; 18648 18649 /** 18650 * @private 18651 * @param {LocaleInfo} locinfo 18652 * @param {function(DurationFmt)|undefined} onLoad 18653 */ 18654 DurationFmt.prototype._init = function(locinfo, onLoad) { 18655 var digits; 18656 if (typeof(this.useNative) === 'boolean') { 18657 // if the caller explicitly said to use native or not, honour that despite what the locale data says... 18658 if (this.useNative) { 18659 digits = locinfo.getNativeDigits(); 18660 if (digits) { 18661 this.digits = digits; 18662 } 18663 } 18664 } else if (locinfo.getDigitsStyle() === "native") { 18665 // else if the locale usually uses native digits, then use them 18666 digits = locinfo.getNativeDigits(); 18667 if (digits) { 18668 this.useNative = true; 18669 this.digits = digits; 18670 } 18671 } // else use western digits always 18672 18673 if (typeof(onLoad) === 'function') { 18674 onLoad(this); 18675 } 18676 }; 18677 18678 /** 18679 * Format a duration according to the format template of this formatter instance.<p> 18680 * 18681 * The components parameter should be an object that contains any or all of these 18682 * numeric properties: 18683 * 18684 * <ul> 18685 * <li>year 18686 * <li>month 18687 * <li>week 18688 * <li>day 18689 * <li>hour 18690 * <li>minute 18691 * <li>second 18692 * </ul> 18693 * <p> 18694 * 18695 * When a property is left out of the components parameter or has a value of 0, it will not 18696 * be formatted into the output string, except for times that include 0 minutes and 0 seconds. 18697 * 18698 * This formatter will not ensure that numbers for each component property is within the 18699 * valid range for that component. This allows you to format durations that are longer 18700 * than normal range. For example, you could format a duration has being "33 hours" rather 18701 * than "1 day, 9 hours". 18702 * 18703 * @param {Object} components date/time components to be formatted into a duration string 18704 * @return {IString} a string with the duration formatted according to the style and 18705 * locale set up for this formatter instance. If the components parameter is empty or 18706 * undefined, an empty string is returned. 18707 */ 18708 DurationFmt.prototype.format = function (components) { 18709 var i, list, temp, fmt, secondlast = true, str = ""; 18710 18711 list = DurationFmt.complist[this.style]; 18712 //for (i = 0; i < list.length; i++) { 18713 for (i = list.length-1; i >= 0; i--) { 18714 //console.log("Now dealing with " + list[i]); 18715 if (typeof(components[list[i]]) !== 'undefined' && components[list[i]] != 0) { 18716 if (str.length > 0) { 18717 str = ((this.length === 'full' && secondlast) ? this.components.finalSeparator : this.components.separator) + str; 18718 secondlast = false; 18719 } 18720 str = this.components[list[i]].formatChoice(components[list[i]], {num: this._mapDigits(components[list[i]])}) + str; 18721 } 18722 } 18723 18724 if (this.style === 'clock') { 18725 if (typeof(components.hour) !== 'undefined') { 18726 fmt = (typeof(components.second) !== 'undefined') ? this.timeFmtHMS : this.timeFmtHM; 18727 } else { 18728 fmt = this.timeFmtMS; 18729 } 18730 18731 if (str.length > 0) { 18732 str += this.components.separator; 18733 } 18734 str += fmt._formatTemplate(components, fmt.templateArr); 18735 } 18736 18737 return new IString(str); 18738 }; 18739 18740 /** 18741 * Return the locale that was used to construct this duration formatter object. If the 18742 * locale was not given as parameter to the constructor, this method returns the default 18743 * locale of the system. 18744 * 18745 * @return {Locale} locale that this duration formatter was constructed with 18746 */ 18747 DurationFmt.prototype.getLocale = function () { 18748 return this.locale; 18749 }; 18750 18751 /** 18752 * Return the length that was used to construct this duration formatter object. If the 18753 * length was not given as parameter to the constructor, this method returns the default 18754 * length. Valid values are "short", "medium", "long", and "full". 18755 * 18756 * @return {string} length that this duration formatter was constructed with 18757 */ 18758 DurationFmt.prototype.getLength = function () { 18759 return this.length; 18760 }; 18761 18762 /** 18763 * Return the style that was used to construct this duration formatter object. Returns 18764 * one of "text" or "clock". 18765 * 18766 * @return {string} style that this duration formatter was constructed with 18767 */ 18768 DurationFmt.prototype.getStyle = function () { 18769 return this.style; 18770 }; 18771 18772 18773 /*< isAlpha.js */ 18774 /* 18775 * ctype.islpha.js - Character type is alphabetic 18776 * 18777 * Copyright © 2012-2015, JEDLSoft 18778 * 18779 * Licensed under the Apache License, Version 2.0 (the "License"); 18780 * you may not use this file except in compliance with the License. 18781 * You may obtain a copy of the License at 18782 * 18783 * http://www.apache.org/licenses/LICENSE-2.0 18784 * 18785 * Unless required by applicable law or agreed to in writing, software 18786 * distributed under the License is distributed on an "AS IS" BASIS, 18787 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18788 * 18789 * See the License for the specific language governing permissions and 18790 * limitations under the License. 18791 */ 18792 18793 // !depends CType.js IString.js ilib.js 18794 18795 // !data ctype_l 18796 18797 18798 /** 18799 * Return whether or not the first character is alphabetic.<p> 18800 * 18801 * @static 18802 * @param {string|IString|number} ch character or code point to examine 18803 * @return {boolean} true if the first character is alphabetic. 18804 */ 18805 var isAlpha = function (ch) { 18806 var num; 18807 switch (typeof(ch)) { 18808 case 'number': 18809 num = ch; 18810 break; 18811 case 'string': 18812 num = IString.toCodePoint(ch, 0); 18813 break; 18814 case 'undefined': 18815 return false; 18816 default: 18817 num = ch._toCodePoint(0); 18818 break; 18819 } 18820 return CType._inRange(num, 'Lu', ilib.data.ctype_l) || 18821 CType._inRange(num, 'Ll', ilib.data.ctype_l) || 18822 CType._inRange(num, 'Lt', ilib.data.ctype_l) || 18823 CType._inRange(num, 'Lm', ilib.data.ctype_l) || 18824 CType._inRange(num, 'Lo', ilib.data.ctype_l); 18825 }; 18826 18827 /** 18828 * @protected 18829 * @param {boolean} sync 18830 * @param {Object|undefined} loadParams 18831 * @param {function(*)|undefined} onLoad 18832 */ 18833 isAlpha._init = function (sync, loadParams, onLoad) { 18834 CType._load("ctype_l", sync, loadParams, onLoad); 18835 }; 18836 18837 18838 /*< isAlnum.js */ 18839 /* 18840 * isAlnum.js - Character type is alphanumeric 18841 * 18842 * Copyright © 2012-2015, JEDLSoft 18843 * 18844 * Licensed under the Apache License, Version 2.0 (the "License"); 18845 * you may not use this file except in compliance with the License. 18846 * You may obtain a copy of the License at 18847 * 18848 * http://www.apache.org/licenses/LICENSE-2.0 18849 * 18850 * Unless required by applicable law or agreed to in writing, software 18851 * distributed under the License is distributed on an "AS IS" BASIS, 18852 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18853 * 18854 * See the License for the specific language governing permissions and 18855 * limitations under the License. 18856 */ 18857 18858 // !depends CType.js IString.js isAlpha.js isDigit.js 18859 18860 18861 /** 18862 * Return whether or not the first character is alphabetic or numeric.<p> 18863 * 18864 * @static 18865 * @param {string|IString|number} ch character or code point to examine 18866 * @return {boolean} true if the first character is alphabetic or numeric 18867 */ 18868 var isAlnum = function (ch) { 18869 var num; 18870 switch (typeof(ch)) { 18871 case 'number': 18872 num = ch; 18873 break; 18874 case 'string': 18875 num = IString.toCodePoint(ch, 0); 18876 break; 18877 case 'undefined': 18878 return false; 18879 default: 18880 num = ch._toCodePoint(0); 18881 break; 18882 } 18883 return isAlpha(num) || isDigit(num); 18884 }; 18885 18886 /** 18887 * @protected 18888 * @param {boolean} sync 18889 * @param {Object|undefined} loadParams 18890 * @param {function(*)|undefined} onLoad 18891 */ 18892 isAlnum._init = function (sync, loadParams, onLoad) { 18893 isAlpha._init(sync, loadParams, function () { 18894 isDigit._init(sync, loadParams, onLoad); 18895 }); 18896 }; 18897 18898 18899 18900 /*< isAscii.js */ 18901 /* 18902 * isAscii.js - Character type is ASCII 18903 * 18904 * Copyright © 2012-2015, JEDLSoft 18905 * 18906 * Licensed under the Apache License, Version 2.0 (the "License"); 18907 * you may not use this file except in compliance with the License. 18908 * You may obtain a copy of the License at 18909 * 18910 * http://www.apache.org/licenses/LICENSE-2.0 18911 * 18912 * Unless required by applicable law or agreed to in writing, software 18913 * distributed under the License is distributed on an "AS IS" BASIS, 18914 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18915 * 18916 * See the License for the specific language governing permissions and 18917 * limitations under the License. 18918 */ 18919 18920 // !depends CType.js IString.js ilib.js 18921 18922 // !data ctype 18923 18924 18925 /** 18926 * Return whether or not the first character is in the ASCII range.<p> 18927 * 18928 * @static 18929 * @param {string|IString|number} ch character or code point to examine 18930 * @return {boolean} true if the first character is in the ASCII range. 18931 */ 18932 var isAscii = function (ch) { 18933 var num; 18934 switch (typeof(ch)) { 18935 case 'number': 18936 num = ch; 18937 break; 18938 case 'string': 18939 num = IString.toCodePoint(ch, 0); 18940 break; 18941 case 'undefined': 18942 return false; 18943 default: 18944 num = ch._toCodePoint(0); 18945 break; 18946 } 18947 return CType._inRange(num, 'ascii', ilib.data.ctype); 18948 }; 18949 18950 /** 18951 * @protected 18952 * @param {boolean} sync 18953 * @param {Object|undefined} loadParams 18954 * @param {function(*)|undefined} onLoad 18955 */ 18956 isAscii._init = function (sync, loadParams, onLoad) { 18957 CType._init(sync, loadParams, onLoad); 18958 }; 18959 18960 18961 /*< isBlank.js */ 18962 /* 18963 * isBlank.js - Character type is blank 18964 * 18965 * Copyright © 2012-2015, JEDLSoft 18966 * 18967 * Licensed under the Apache License, Version 2.0 (the "License"); 18968 * you may not use this file except in compliance with the License. 18969 * You may obtain a copy of the License at 18970 * 18971 * http://www.apache.org/licenses/LICENSE-2.0 18972 * 18973 * Unless required by applicable law or agreed to in writing, software 18974 * distributed under the License is distributed on an "AS IS" BASIS, 18975 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18976 * 18977 * See the License for the specific language governing permissions and 18978 * limitations under the License. 18979 */ 18980 18981 // !depends CType.js IString.js ilib.js 18982 18983 // !data ctype 18984 18985 18986 /** 18987 * Return whether or not the first character is a blank character.<p> 18988 * 18989 * @static 18990 * ie. a space or a tab. 18991 * @param {string|IString|number} ch character or code point to examine 18992 * @return {boolean} true if the first character is a blank character. 18993 */ 18994 var isBlank = function (ch) { 18995 var num; 18996 switch (typeof(ch)) { 18997 case 'number': 18998 num = ch; 18999 break; 19000 case 'string': 19001 num = IString.toCodePoint(ch, 0); 19002 break; 19003 case 'undefined': 19004 return false; 19005 default: 19006 num = ch._toCodePoint(0); 19007 break; 19008 } 19009 return CType._inRange(num, 'blank', ilib.data.ctype); 19010 }; 19011 19012 /** 19013 * @protected 19014 * @param {boolean} sync 19015 * @param {Object|undefined} loadParams 19016 * @param {function(*)|undefined} onLoad 19017 */ 19018 isBlank._init = function (sync, loadParams, onLoad) { 19019 CType._init(sync, loadParams, onLoad); 19020 }; 19021 19022 19023 /*< isCntrl.js */ 19024 /* 19025 * isCntrl.js - Character type is control character 19026 * 19027 * Copyright © 2012-2015, JEDLSoft 19028 * 19029 * Licensed under the Apache License, Version 2.0 (the "License"); 19030 * you may not use this file except in compliance with the License. 19031 * You may obtain a copy of the License at 19032 * 19033 * http://www.apache.org/licenses/LICENSE-2.0 19034 * 19035 * Unless required by applicable law or agreed to in writing, software 19036 * distributed under the License is distributed on an "AS IS" BASIS, 19037 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19038 * 19039 * See the License for the specific language governing permissions and 19040 * limitations under the License. 19041 */ 19042 19043 // !depends CType.js IString.js ilib.js 19044 19045 // !data ctype_c 19046 19047 19048 /** 19049 * Return whether or not the first character is a control character.<p> 19050 * 19051 * @static 19052 * @param {string|IString|number} ch character or code point to examine 19053 * @return {boolean} true if the first character is a control character. 19054 */ 19055 var isCntrl = function (ch) { 19056 var num; 19057 switch (typeof(ch)) { 19058 case 'number': 19059 num = ch; 19060 break; 19061 case 'string': 19062 num = IString.toCodePoint(ch, 0); 19063 break; 19064 case 'undefined': 19065 return false; 19066 default: 19067 num = ch._toCodePoint(0); 19068 break; 19069 } 19070 return CType._inRange(num, 'Cc', ilib.data.ctype_c); 19071 }; 19072 19073 /** 19074 * @protected 19075 * @param {boolean} sync 19076 * @param {Object|undefined} loadParams 19077 * @param {function(*)|undefined} onLoad 19078 */ 19079 isCntrl._init = function (sync, loadParams, onLoad) { 19080 CType._load("ctype_c", sync, loadParams, onLoad); 19081 }; 19082 19083 19084 /*< isGraph.js */ 19085 /* 19086 * isGraph.js - Character type is graph char 19087 * 19088 * Copyright © 2012-2015, JEDLSoft 19089 * 19090 * Licensed under the Apache License, Version 2.0 (the "License"); 19091 * you may not use this file except in compliance with the License. 19092 * You may obtain a copy of the License at 19093 * 19094 * http://www.apache.org/licenses/LICENSE-2.0 19095 * 19096 * Unless required by applicable law or agreed to in writing, software 19097 * distributed under the License is distributed on an "AS IS" BASIS, 19098 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19099 * 19100 * See the License for the specific language governing permissions and 19101 * limitations under the License. 19102 */ 19103 19104 // !depends IString.js isSpace.js isCntrl.js ilib.js 19105 19106 19107 /** 19108 * Return whether or not the first character is any printable character 19109 * other than space.<p> 19110 * 19111 * @static 19112 * @param {string|IString|number} ch character or code point to examine 19113 * @return {boolean} true if the first character is any printable character 19114 * other than space. 19115 */ 19116 var isGraph = function (ch) { 19117 var num; 19118 switch (typeof(ch)) { 19119 case 'number': 19120 num = ch; 19121 break; 19122 case 'string': 19123 num = IString.toCodePoint(ch, 0); 19124 break; 19125 case 'undefined': 19126 return false; 19127 default: 19128 num = ch._toCodePoint(0); 19129 break; 19130 } 19131 return typeof(ch) !== 'undefined' && ch.length > 0 && !isSpace(num) && !isCntrl(num); 19132 }; 19133 19134 /** 19135 * @protected 19136 * @param {boolean} sync 19137 * @param {Object|undefined} loadParams 19138 * @param {function(*)|undefined} onLoad 19139 */ 19140 isGraph._init = function (sync, loadParams, onLoad) { 19141 isSpace._init(sync, loadParams, function () { 19142 isCntrl._init(sync, loadParams, onLoad); 19143 }); 19144 }; 19145 19146 19147 19148 /*< isIdeo.js */ 19149 /* 19150 * CType.js - Character type definitions 19151 * 19152 * Copyright © 2012-2015, JEDLSoft 19153 * 19154 * Licensed under the Apache License, Version 2.0 (the "License"); 19155 * you may not use this file except in compliance with the License. 19156 * You may obtain a copy of the License at 19157 * 19158 * http://www.apache.org/licenses/LICENSE-2.0 19159 * 19160 * Unless required by applicable law or agreed to in writing, software 19161 * distributed under the License is distributed on an "AS IS" BASIS, 19162 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19163 * 19164 * See the License for the specific language governing permissions and 19165 * limitations under the License. 19166 */ 19167 19168 // !depends CType.js IString.js 19169 19170 // !data ctype 19171 19172 19173 /** 19174 * Return whether or not the first character is an ideographic character.<p> 19175 * 19176 * @static 19177 * @param {string|IString|number} ch character or code point to examine 19178 * @return {boolean} true if the first character is an ideographic character. 19179 */ 19180 var isIdeo = function (ch) { 19181 var num; 19182 switch (typeof(ch)) { 19183 case 'number': 19184 num = ch; 19185 break; 19186 case 'string': 19187 num = IString.toCodePoint(ch, 0); 19188 break; 19189 case 'undefined': 19190 return false; 19191 default: 19192 num = ch._toCodePoint(0); 19193 break; 19194 } 19195 19196 return CType._inRange(num, 'cjk', ilib.data.ctype) || 19197 CType._inRange(num, 'cjkradicals', ilib.data.ctype) || 19198 CType._inRange(num, 'enclosedcjk', ilib.data.ctype) || 19199 CType._inRange(num, 'cjkpunct', ilib.data.ctype) || 19200 CType._inRange(num, 'cjkcompatibility', ilib.data.ctype); 19201 }; 19202 19203 /** 19204 * @protected 19205 * @param {boolean} sync 19206 * @param {Object|undefined} loadParams 19207 * @param {function(*)|undefined} onLoad 19208 */ 19209 isIdeo._init = function (sync, loadParams, onLoad) { 19210 CType._init(sync, loadParams, onLoad); 19211 }; 19212 19213 19214 /*< isLower.js */ 19215 /* 19216 * isLower.js - Character type is lower case letter 19217 * 19218 * Copyright © 2012-2015, JEDLSoft 19219 * 19220 * Licensed under the Apache License, Version 2.0 (the "License"); 19221 * you may not use this file except in compliance with the License. 19222 * You may obtain a copy of the License at 19223 * 19224 * http://www.apache.org/licenses/LICENSE-2.0 19225 * 19226 * Unless required by applicable law or agreed to in writing, software 19227 * distributed under the License is distributed on an "AS IS" BASIS, 19228 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19229 * 19230 * See the License for the specific language governing permissions and 19231 * limitations under the License. 19232 */ 19233 19234 // !depends CType.js IString.js 19235 19236 // !data ctype_l 19237 19238 19239 19240 /** 19241 * Return whether or not the first character is lower-case. For alphabetic 19242 * characters in scripts that do not make a distinction between upper- and 19243 * lower-case, this function always returns true.<p> 19244 * 19245 * @static 19246 * @param {string|IString|number} ch character or code point to examine 19247 * @return {boolean} true if the first character is lower-case. 19248 */ 19249 var isLower = function (ch) { 19250 var num; 19251 switch (typeof(ch)) { 19252 case 'number': 19253 num = ch; 19254 break; 19255 case 'string': 19256 num = IString.toCodePoint(ch, 0); 19257 break; 19258 case 'undefined': 19259 return false; 19260 default: 19261 num = ch._toCodePoint(0); 19262 break; 19263 } 19264 19265 return CType._inRange(num, 'Ll', ilib.data.ctype_l); 19266 }; 19267 19268 /** 19269 * @protected 19270 * @param {boolean} sync 19271 * @param {Object|undefined} loadParams 19272 * @param {function(*)|undefined} onLoad 19273 */ 19274 isLower._init = function (sync, loadParams, onLoad) { 19275 CType._load("ctype_l", sync, loadParams, onLoad); 19276 }; 19277 19278 19279 /*< isPrint.js */ 19280 /* 19281 * isPrint.js - Character type is printable char 19282 * 19283 * Copyright © 2012-2015, JEDLSoft 19284 * 19285 * Licensed under the Apache License, Version 2.0 (the "License"); 19286 * you may not use this file except in compliance with the License. 19287 * You may obtain a copy of the License at 19288 * 19289 * http://www.apache.org/licenses/LICENSE-2.0 19290 * 19291 * Unless required by applicable law or agreed to in writing, software 19292 * distributed under the License is distributed on an "AS IS" BASIS, 19293 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19294 * 19295 * See the License for the specific language governing permissions and 19296 * limitations under the License. 19297 */ 19298 19299 // !depends CType.js isCntrl.js 19300 19301 19302 /** 19303 * Return whether or not the first character is any printable character, 19304 * including space.<p> 19305 * 19306 * @static 19307 * @param {string|IString|number} ch character or code point to examine 19308 * @return {boolean} true if the first character is printable. 19309 */ 19310 var isPrint = function (ch) { 19311 return typeof(ch) !== 'undefined' && ch.length > 0 && !isCntrl(ch); 19312 }; 19313 19314 /** 19315 * @protected 19316 * @param {boolean} sync 19317 * @param {Object|undefined} loadParams 19318 * @param {function(*)|undefined} onLoad 19319 */ 19320 isPrint._init = function (sync, loadParams, onLoad) { 19321 isCntrl._init(sync, loadParams, onLoad); 19322 }; 19323 19324 19325 /*< isPunct.js */ 19326 /* 19327 * isPunct.js - Character type is punctuation 19328 * 19329 * Copyright © 2012-2015, JEDLSoft 19330 * 19331 * Licensed under the Apache License, Version 2.0 (the "License"); 19332 * you may not use this file except in compliance with the License. 19333 * You may obtain a copy of the License at 19334 * 19335 * http://www.apache.org/licenses/LICENSE-2.0 19336 * 19337 * Unless required by applicable law or agreed to in writing, software 19338 * distributed under the License is distributed on an "AS IS" BASIS, 19339 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19340 * 19341 * See the License for the specific language governing permissions and 19342 * limitations under the License. 19343 */ 19344 19345 // !depends CType.js IString.js 19346 19347 // !data ctype_p 19348 19349 19350 19351 /** 19352 * Return whether or not the first character is punctuation.<p> 19353 * 19354 * @static 19355 * @param {string|IString|number} ch character or code point to examine 19356 * @return {boolean} true if the first character is punctuation. 19357 */ 19358 var isPunct = function (ch) { 19359 var num; 19360 switch (typeof(ch)) { 19361 case 'number': 19362 num = ch; 19363 break; 19364 case 'string': 19365 num = IString.toCodePoint(ch, 0); 19366 break; 19367 case 'undefined': 19368 return false; 19369 default: 19370 num = ch._toCodePoint(0); 19371 break; 19372 } 19373 19374 return CType._inRange(num, 'Pd', ilib.data.ctype_p) || 19375 CType._inRange(num, 'Ps', ilib.data.ctype_p) || 19376 CType._inRange(num, 'Pe', ilib.data.ctype_p) || 19377 CType._inRange(num, 'Pc', ilib.data.ctype_p) || 19378 CType._inRange(num, 'Po', ilib.data.ctype_p) || 19379 CType._inRange(num, 'Pi', ilib.data.ctype_p) || 19380 CType._inRange(num, 'Pf', ilib.data.ctype_p); 19381 }; 19382 19383 /** 19384 * @protected 19385 * @param {boolean} sync 19386 * @param {Object|undefined} loadParams 19387 * @param {function(*)|undefined} onLoad 19388 */ 19389 isPunct._init = function (sync, loadParams, onLoad) { 19390 CType._load("ctype_p", sync, loadParams, onLoad); 19391 }; 19392 19393 19394 19395 /*< isUpper.js */ 19396 /* 19397 * isUpper.js - Character type is upper-case letter 19398 * 19399 * Copyright © 2012-2015, JEDLSoft 19400 * 19401 * Licensed under the Apache License, Version 2.0 (the "License"); 19402 * you may not use this file except in compliance with the License. 19403 * You may obtain a copy of the License at 19404 * 19405 * http://www.apache.org/licenses/LICENSE-2.0 19406 * 19407 * Unless required by applicable law or agreed to in writing, software 19408 * distributed under the License is distributed on an "AS IS" BASIS, 19409 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19410 * 19411 * See the License for the specific language governing permissions and 19412 * limitations under the License. 19413 */ 19414 19415 // !depends CType.js IString.js 19416 19417 // !data ctype_l 19418 19419 19420 19421 /** 19422 * Return whether or not the first character is upper-case. For alphabetic 19423 * characters in scripts that do not make a distinction between upper- and 19424 * lower-case, this function always returns true.<p> 19425 * 19426 * @static 19427 * @param {string|IString|number} ch character or code point to examine 19428 * @return {boolean} true if the first character is upper-case. 19429 */ 19430 var isUpper = function (ch) { 19431 var num; 19432 switch (typeof(ch)) { 19433 case 'number': 19434 num = ch; 19435 break; 19436 case 'string': 19437 num = IString.toCodePoint(ch, 0); 19438 break; 19439 case 'undefined': 19440 return false; 19441 default: 19442 num = ch._toCodePoint(0); 19443 break; 19444 } 19445 19446 return CType._inRange(num, 'Lu', ilib.data.ctype_l); 19447 }; 19448 19449 /** 19450 * @protected 19451 * @param {boolean} sync 19452 * @param {Object|undefined} loadParams 19453 * @param {function(*)|undefined} onLoad 19454 */ 19455 isUpper._init = function (sync, loadParams, onLoad) { 19456 CType._load("ctype_l", sync, loadParams, onLoad); 19457 }; 19458 19459 19460 /*< isXdigit.js */ 19461 /* 19462 * isXdigit.js - Character type is hex digit 19463 * 19464 * Copyright © 2012-2015, JEDLSoft 19465 * 19466 * Licensed under the Apache License, Version 2.0 (the "License"); 19467 * you may not use this file except in compliance with the License. 19468 * You may obtain a copy of the License at 19469 * 19470 * http://www.apache.org/licenses/LICENSE-2.0 19471 * 19472 * Unless required by applicable law or agreed to in writing, software 19473 * distributed under the License is distributed on an "AS IS" BASIS, 19474 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19475 * 19476 * See the License for the specific language governing permissions and 19477 * limitations under the License. 19478 */ 19479 19480 // !depends CType.js IString.js 19481 19482 // !data ctype 19483 19484 19485 19486 /** 19487 * Return whether or not the first character is a hexadecimal digit written 19488 * in the Latin script. (0-9 or A-F)<p> 19489 * 19490 * @static 19491 * @param {string|IString|number} ch character or code point to examine 19492 * @return {boolean} true if the first character is a hexadecimal digit written 19493 * in the Latin script. 19494 */ 19495 var isXdigit = function (ch) { 19496 var num; 19497 switch (typeof(ch)) { 19498 case 'number': 19499 num = ch; 19500 break; 19501 case 'string': 19502 num = IString.toCodePoint(ch, 0); 19503 break; 19504 case 'undefined': 19505 return false; 19506 default: 19507 num = ch._toCodePoint(0); 19508 break; 19509 } 19510 19511 return CType._inRange(num, 'xdigit', ilib.data.ctype); 19512 }; 19513 19514 /** 19515 * @protected 19516 * @param {boolean} sync 19517 * @param {Object|undefined} loadParams 19518 * @param {function(*)|undefined} onLoad 19519 */ 19520 isXdigit._init = function (sync, loadParams, onLoad) { 19521 CType._init(sync, loadParams, onLoad); 19522 }; 19523 19524 19525 /*< isScript.js */ 19526 /* 19527 * isScript.js - Character type is script 19528 * 19529 * Copyright © 2012-2015, JEDLSoft 19530 * 19531 * Licensed under the Apache License, Version 2.0 (the "License"); 19532 * you may not use this file except in compliance with the License. 19533 * You may obtain a copy of the License at 19534 * 19535 * http://www.apache.org/licenses/LICENSE-2.0 19536 * 19537 * Unless required by applicable law or agreed to in writing, software 19538 * distributed under the License is distributed on an "AS IS" BASIS, 19539 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19540 * 19541 * See the License for the specific language governing permissions and 19542 * limitations under the License. 19543 */ 19544 19545 // !depends CType.js IString.js 19546 19547 // !data scriptToRange 19548 19549 19550 19551 /** 19552 * Return whether or not the first character in the given string is 19553 * in the given script. The script is given as the 4-letter ISO 19554 * 15924 script code.<p> 19555 * 19556 * @static 19557 * @param {string|IString|number} ch character or code point to examine 19558 * @param {string} script the 4-letter ISO 15924 to query against 19559 * @return {boolean} true if the first character is in the given script, and 19560 * false otherwise 19561 */ 19562 var isScript = function (ch, script) { 19563 var num; 19564 switch (typeof(ch)) { 19565 case 'number': 19566 num = ch; 19567 break; 19568 case 'string': 19569 num = IString.toCodePoint(ch, 0); 19570 break; 19571 case 'undefined': 19572 return false; 19573 default: 19574 num = ch._toCodePoint(0); 19575 break; 19576 } 19577 19578 return CType._inRange(num, script, ilib.data.scriptToRange); 19579 }; 19580 19581 /** 19582 * @protected 19583 * @param {boolean} sync 19584 * @param {Object|undefined} loadParams 19585 * @param {function(*)|undefined} onLoad 19586 */ 19587 isScript._init = function (sync, loadParams, onLoad) { 19588 CType._load("scriptToRange", sync, loadParams, onLoad); 19589 }; 19590 19591 19592 /*< ScriptInfo.js */ 19593 /* 19594 * ScriptInfo.js - information about scripts 19595 * 19596 * Copyright © 2012-2015, JEDLSoft 19597 * 19598 * Licensed under the Apache License, Version 2.0 (the "License"); 19599 * you may not use this file except in compliance with the License. 19600 * You may obtain a copy of the License at 19601 * 19602 * http://www.apache.org/licenses/LICENSE-2.0 19603 * 19604 * Unless required by applicable law or agreed to in writing, software 19605 * distributed under the License is distributed on an "AS IS" BASIS, 19606 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19607 * 19608 * See the License for the specific language governing permissions and 19609 * limitations under the License. 19610 */ 19611 19612 // !depends ilib.js Utils.js 19613 19614 // !data scripts 19615 19616 19617 /** 19618 * @class 19619 * Create a new script info instance. This class encodes information about 19620 * scripts, which are sets of characters used in a writing system.<p> 19621 * 19622 * The options object may contain any of the following properties: 19623 * 19624 * <ul> 19625 * <li><i>onLoad</i> - a callback function to call when the script info object is fully 19626 * loaded. When the onLoad option is given, the script info object will attempt to 19627 * load any missing locale data using the ilib loader callback. 19628 * When the constructor is done (even if the data is already preassembled), the 19629 * onLoad function is called with the current instance as a parameter, so this 19630 * callback can be used with preassembled or dynamic loading or a mix of the two. 19631 * 19632 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 19633 * asynchronously. If this option is given as "false", then the "onLoad" 19634 * callback must be given, as the instance returned from this constructor will 19635 * not be usable for a while. 19636 * 19637 * <li><i>loadParams</i> - an object containing parameters to pass to the 19638 * loader callback function when locale data is missing. The parameters are not 19639 * interpretted or modified in any way. They are simply passed along. The object 19640 * may contain any property/value pairs as long as the calling code is in 19641 * agreement with the loader callback function as to what those parameters mean. 19642 * </ul> 19643 * 19644 * 19645 * @constructor 19646 * @param {string} script The ISO 15924 4-letter identifier for the script 19647 * @param {Object} options parameters to initialize this matcher 19648 */ 19649 var ScriptInfo = function(script, options) { 19650 var sync = true, 19651 loadParams = undefined; 19652 19653 this.script = script; 19654 19655 if (options) { 19656 if (typeof(options.sync) !== 'undefined') { 19657 sync = (options.sync == true); 19658 } 19659 19660 if (typeof(options.loadParams) !== 'undefined') { 19661 loadParams = options.loadParams; 19662 } 19663 } 19664 19665 if (!ScriptInfo.cache) { 19666 ScriptInfo.cache = {}; 19667 } 19668 19669 if (!ilib.data.scripts) { 19670 Utils.loadData({ 19671 object: ScriptInfo, 19672 locale: "-", 19673 name: "scripts.json", 19674 sync: sync, 19675 loadParams: loadParams, 19676 callback: ilib.bind(this, function (info) { 19677 if (!info) { 19678 info = {"Latn":{"nb":215,"nm":"Latin","lid":"Latin","rtl":false,"ime":false,"casing":true}}; 19679 var spec = this.locale.getSpec().replace(/-/g, "_"); 19680 ScriptInfo.cache[spec] = info; 19681 } 19682 ilib.data.scripts = info; 19683 this.info = script && ilib.data.scripts[script]; 19684 if (options && typeof(options.onLoad) === 'function') { 19685 options.onLoad(this); 19686 } 19687 }) 19688 }); 19689 } else { 19690 this.info = ilib.data.scripts[script]; 19691 } 19692 19693 }; 19694 19695 /** 19696 * @private 19697 */ 19698 ScriptInfo._getScriptsArray = function() { 19699 var ret = [], 19700 script = undefined, 19701 scripts = ilib.data.scripts; 19702 19703 for (script in scripts) { 19704 if (script && scripts[script]) { 19705 ret.push(script); 19706 } 19707 } 19708 19709 return ret; 19710 }; 19711 19712 /** 19713 * Return an array of all ISO 15924 4-letter identifier script identifiers that 19714 * this copy of ilib knows about. 19715 * @static 19716 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 19717 * @param {Object} loadParams arbitrary object full of properties to pass to the loader 19718 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 19719 * @return {Array.<string>} an array of all script identifiers that this copy of 19720 * ilib knows about 19721 */ 19722 ScriptInfo.getAllScripts = function(sync, loadParams, onLoad) { 19723 if (!ilib.data.scripts) { 19724 Utils.loadData({ 19725 object: ScriptInfo, 19726 locale: "-", 19727 name: "scripts.json", 19728 sync: sync, 19729 loadParams: loadParams, 19730 callback: ilib.bind(this, function (info) { 19731 ilib.data.scripts = info; 19732 19733 if (typeof(onLoad) === 'function') { 19734 onLoad(ScriptInfo._getScriptsArray()); 19735 } 19736 }) 19737 }); 19738 } 19739 19740 return ScriptInfo._getScriptsArray(); 19741 }; 19742 19743 ScriptInfo.prototype = { 19744 /** 19745 * Return the 4-letter ISO 15924 identifier associated 19746 * with this script. 19747 * @return {string} the 4-letter ISO code for this script 19748 */ 19749 getCode: function () { 19750 return this.info && this.script; 19751 }, 19752 19753 /** 19754 * Get the ISO 15924 code number associated with this 19755 * script. 19756 * 19757 * @return {number} the ISO 15924 code number 19758 */ 19759 getCodeNumber: function () { 19760 return this.info && this.info.nb || 0; 19761 }, 19762 19763 /** 19764 * Get the name of this script in English. 19765 * 19766 * @return {string} the name of this script in English 19767 */ 19768 getName: function () { 19769 return this.info && this.info.nm; 19770 }, 19771 19772 /** 19773 * Get the long identifier assciated with this script. 19774 * 19775 * @return {string} the long identifier of this script 19776 */ 19777 getLongCode: function () { 19778 return this.info && this.info.lid; 19779 }, 19780 19781 /** 19782 * Return the usual direction that text in this script is written 19783 * in. Possible return values are "rtl" for right-to-left, 19784 * "ltr" for left-to-right, and "ttb" for top-to-bottom. 19785 * 19786 * @return {string} the usual direction that text in this script is 19787 * written in 19788 */ 19789 getScriptDirection: function() { 19790 return (this.info && typeof(this.info.rtl) !== 'undefined' && this.info.rtl) ? "rtl" : "ltr"; 19791 }, 19792 19793 /** 19794 * Return true if this script typically requires an input method engine 19795 * to enter its characters. 19796 * 19797 * @return {boolean} true if this script typically requires an IME 19798 */ 19799 getNeedsIME: function () { 19800 return this.info && this.info.ime ? true : false; // converts undefined to false 19801 }, 19802 19803 /** 19804 * Return true if this script uses lower- and upper-case characters. 19805 * 19806 * @return {boolean} true if this script uses letter case 19807 */ 19808 getCasing: function () { 19809 return this.info && this.info.casing ? true : false; // converts undefined to false 19810 } 19811 }; 19812 19813 19814 /*< Name.js */ 19815 /* 19816 * Name.js - Person name parser 19817 * 19818 * Copyright © 2013-2015, JEDLSoft 19819 * 19820 * Licensed under the Apache License, Version 2.0 (the "License"); 19821 * you may not use this file except in compliance with the License. 19822 * You may obtain a copy of the License at 19823 * 19824 * http://www.apache.org/licenses/LICENSE-2.0 19825 * 19826 * Unless required by applicable law or agreed to in writing, software 19827 * distributed under the License is distributed on an "AS IS" BASIS, 19828 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19829 * 19830 * See the License for the specific language governing permissions and 19831 * limitations under the License. 19832 */ 19833 19834 /* !depends 19835 ilib.js 19836 Locale.js 19837 Utils.js 19838 isAlpha.js 19839 isIdeo.js 19840 isPunct.js 19841 isSpace.js 19842 JSUtils.js 19843 IString.js 19844 */ 19845 19846 // !data name 19847 19848 // notes: 19849 // icelandic given names: http://en.wiktionary.org/wiki/Appendix:Icelandic_given_names 19850 // danish approved given names: http://www.familiestyrelsen.dk/samliv/navne/ 19851 // http://www.mentalfloss.com/blogs/archives/59277 19852 // other countries with first name restrictions: Norway, China, New Zealand, Japan, Sweden, Germany, Hungary 19853 19854 19855 19856 /** 19857 * @class 19858 * A class to parse names of people. Different locales have different conventions when it 19859 * comes to naming people.<p> 19860 * 19861 * The options can contain any of the following properties: 19862 * 19863 * <ul> 19864 * <li><i>locale</i> - use the rules and conventions of the given locale in order to parse 19865 * the name 19866 * <li><i>style</i> - explicitly use the named style to parse the name. Valid values so 19867 * far are "western" and "asian". If this property is not specified, then the style will 19868 * be gleaned from the name itself. This class will count the total number of Latin or Asian 19869 * characters. If the majority of the characters are in one style, that style will be 19870 * used to parse the whole name. 19871 * <li><i>order</i> - explicitly use the given order for names. In some locales, such 19872 * as Russian, names may be written equally validly as "givenName familyName" or "familyName 19873 * givenName". This option tells the parser which order to prefer, and overrides the 19874 * default order for the locale. Valid values are "gf" (given-family) or "fg" (family-given). 19875 * <li><i>useSpaces</i> - explicitly specifies whether to use spaces or not between the given name , middle name 19876 * and family name. 19877 * <li>onLoad - a callback function to call when the name info is fully 19878 * loaded and the name has been parsed. When the onLoad option is given, the name object 19879 * will attempt to load any missing locale data using the ilib loader callback. 19880 * When the constructor is done (even if the data is already preassembled), the 19881 * onLoad function is called with the current instance as a parameter, so this 19882 * callback can be used with preassembled or dynamic loading or a mix of the two. 19883 * 19884 * <li>sync - tell whether to load any missing locale data synchronously or 19885 * asynchronously. If this option is given as "false", then the "onLoad" 19886 * callback must be given, as the instance returned from this constructor will 19887 * not be usable for a while. 19888 * 19889 * <li><i>loadParams</i> - an object containing parameters to pass to the 19890 * loader callback function when locale data is missing. The parameters are not 19891 * interpretted or modified in any way. They are simply passed along. The object 19892 * may contain any property/value pairs as long as the calling code is in 19893 * agreement with the loader callback function as to what those parameters mean. 19894 * </ul> 19895 * 19896 * When the parser has completed its parsing, it fills in the fields listed below.<p> 19897 * 19898 * For names that include auxilliary words, such as the family name "van der Heijden", all 19899 * of the auxilliary words ("van der") will be included in the field.<p> 19900 * 19901 * For names in Spanish locales, it is assumed that the family name is doubled. That is, 19902 * a person may have a paternal family name followed by a maternal family name. All 19903 * family names will be listed in the familyName field as normal, separated by spaces. 19904 * When formatting the short version of such names, only the paternal family name will 19905 * be used. 19906 * 19907 * 19908 * @constructor 19909 * @param {string|Name=} name the name to parse 19910 * @param {Object=} options Options governing the construction of this name instance 19911 */ 19912 var Name = function (name, options) { 19913 var sync = true; 19914 19915 if (!name || name.length === 0) { 19916 return; 19917 } 19918 19919 this.loadParams = {}; 19920 19921 if (options) { 19922 if (options.locale) { 19923 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 19924 } 19925 19926 if (options.style && (options.style === "asian" || options.style === "western")) { 19927 this.style = options.style; 19928 } 19929 19930 if (options.order && (options.order === "gmf" || options.order === "fmg" || options.order === "fgm")) { 19931 this.order = options.order; 19932 } 19933 19934 if (typeof (options.sync) !== 'undefined') { 19935 sync = (options.sync == true); 19936 } 19937 19938 if (typeof (options.loadParams) !== 'undefined') { 19939 this.loadParams = options.loadParams; 19940 } 19941 } 19942 19943 if (!Name.cache) { 19944 Name.cache = {}; 19945 } 19946 19947 this.locale = this.locale || new Locale(); 19948 19949 isAlpha._init(sync, this.loadParams, ilib.bind(this, function() { 19950 isIdeo._init(sync, this.loadParams, ilib.bind(this, function() { 19951 isPunct._init(sync, this.loadParams, ilib.bind(this, function() { 19952 isSpace._init(sync, this.loadParams, ilib.bind(this, function() { 19953 Utils.loadData({ 19954 object: Name, 19955 locale: this.locale, 19956 name: "name.json", 19957 sync: sync, 19958 loadParams: this.loadParams, 19959 callback: ilib.bind(this, function (info) { 19960 if (!info) { 19961 info = Name.defaultInfo; 19962 var spec = this.locale.getSpec().replace(/-/g, "_"); 19963 Name.cache[spec] = info; 19964 } 19965 if (typeof (name) === 'object') { 19966 // copy constructor 19967 /** 19968 * The prefixes for this name 19969 * @type {string|Array.<string>} 19970 */ 19971 this.prefix = name.prefix; 19972 /** 19973 * The given (personal) name in this name. 19974 * @type {string|Array.<string>} 19975 */ 19976 this.givenName = name.givenName; 19977 /** 19978 * The middle names used in this name. If there are multiple middle names, they all 19979 * appear in this field separated by spaces. 19980 * @type {string|Array.<string>} 19981 */ 19982 this.middleName = name.middleName; 19983 /** 19984 * The family names in this name. If there are multiple family names, they all 19985 * appear in this field separated by spaces. 19986 * @type {string|Array.<string>} 19987 */ 19988 this.familyName = name.familyName; 19989 /** 19990 * The suffixes for this name. If there are multiple suffixes, they all 19991 * appear in this field separated by spaces. 19992 * @type {string|Array.<string>} 19993 */ 19994 this.suffix = name.suffix; 19995 19996 // private properties 19997 this.locale = name.locale; 19998 this.style = name.style; 19999 this.order = name.order; 20000 this.useSpaces = name.useSpaces; 20001 this.isAsianName = name.isAsianName; 20002 return; 20003 } 20004 /** 20005 * @type {{ 20006 * nameStyle:string, 20007 * order:string, 20008 * prefixes:Array.<string>, 20009 * suffixes:Array.<string>, 20010 * auxillaries:Array.<string>, 20011 * honorifics:Array.<string>, 20012 * knownFamilyNames:Array.<string>, 20013 * noCompoundFamilyNames:boolean, 20014 * sortByHeadWord:boolean 20015 * }} */ 20016 this.info = info; 20017 this._init(name); 20018 if (options && typeof(options.onLoad) === 'function') { 20019 options.onLoad(this); 20020 } 20021 }) 20022 }); 20023 })); 20024 })); 20025 })); 20026 })); 20027 }; 20028 20029 Name.defaultInfo = ilib.data.name || { 20030 "components": { 20031 "short": { 20032 "g": 1, 20033 "f": 1 20034 }, 20035 "medium": { 20036 "g": 1, 20037 "m": 1, 20038 "f": 1 20039 }, 20040 "long": { 20041 "p": 1, 20042 "g": 1, 20043 "m": 1, 20044 "f": 1 20045 }, 20046 "full": { 20047 "p": 1, 20048 "g": 1, 20049 "m": 1, 20050 "f": 1, 20051 "s": 1 20052 } 20053 }, 20054 "format": "{prefix} {givenName} {middleName} {familyName}{suffix}", 20055 "sortByHeadWord": false, 20056 "nameStyle": "western", 20057 "conjunctions": { 20058 "and1": "and", 20059 "and2": "and", 20060 "or1": "or", 20061 "or2": "or" 20062 }, 20063 "auxillaries": { 20064 "von": 1, 20065 "von der": 1, 20066 "von den": 1, 20067 "van": 1, 20068 "van der": 1, 20069 "van de": 1, 20070 "van den": 1, 20071 "de": 1, 20072 "di": 1, 20073 "la": 1, 20074 "lo": 1, 20075 "des": 1, 20076 "le": 1, 20077 "les": 1, 20078 "du": 1, 20079 "de la": 1, 20080 "del": 1, 20081 "de los": 1, 20082 "de las": 1 20083 }, 20084 "prefixes": [ 20085 "doctor", 20086 "dr", 20087 "mr", 20088 "mrs", 20089 "ms", 20090 "mister", 20091 "madame", 20092 "madamoiselle", 20093 "miss", 20094 "monsieur", 20095 "señor", 20096 "señora", 20097 "señorita" 20098 ], 20099 "suffixes": [ 20100 ",", 20101 "junior", 20102 "jr", 20103 "senior", 20104 "sr", 20105 "i", 20106 "ii", 20107 "iii", 20108 "esq", 20109 "phd", 20110 "md" 20111 ], 20112 "patronymicName":[ ], 20113 "familyNames":[ ] 20114 }; 20115 20116 /** 20117 * Return true if the given character is in the range of the Han, Hangul, or kana 20118 * scripts. 20119 * @static 20120 * @protected 20121 */ 20122 Name._isAsianChar = function(c) { 20123 return isIdeo(c) || 20124 CType.withinRange(c, "hangul") || 20125 CType.withinRange(c, "hiragana") || 20126 CType.withinRange(c, "katakana"); 20127 }; 20128 20129 20130 /** 20131 * @static 20132 * @protected 20133 */ 20134 Name._isAsianName = function (name, language) { 20135 // the idea is to count the number of asian chars and the number 20136 // of latin chars. If one is greater than the other, choose 20137 // that style. 20138 var asian = 0, 20139 latin = 0, 20140 i; 20141 20142 if (name && name.length > 0) { 20143 for (i = 0; i < name.length; i++) { 20144 var c = name.charAt(i); 20145 20146 if (Name._isAsianChar(c)) { 20147 if (language =="ko" || language =="ja" || language =="zh") { 20148 return true; 20149 } 20150 asian++; 20151 } else if (isAlpha(c)) { 20152 if (!language =="ko" || !language =="ja" || !language =="zh") { 20153 return false; 20154 } 20155 latin++; 20156 } 20157 } 20158 20159 return latin < asian; 20160 } 20161 20162 return false; 20163 }; 20164 20165 /** 20166 * Return true if any Latin letters are found in the string. Return 20167 * false if all the characters are non-Latin. 20168 * @static 20169 * @protected 20170 */ 20171 Name._isEuroName = function (name, language) { 20172 var c, 20173 n = new IString(name), 20174 it = n.charIterator(); 20175 20176 while (it.hasNext()) { 20177 c = it.next(); 20178 20179 if (!Name._isAsianChar(c) && !isPunct(c) && !isSpace(c)) { 20180 return true; 20181 } else if (Name._isAsianChar(c) && (language =="ko" || language =="ja" || language =="zh")) { 20182 return false; 20183 } 20184 } 20185 return false; 20186 }; 20187 20188 Name.prototype = { 20189 /** 20190 * @protected 20191 */ 20192 _init: function (name) { 20193 var parts, prefixArray, prefix, prefixLower, 20194 suffixArray, suffix, suffixLower, 20195 i, info, hpSuffix; 20196 var currentLanguage = this.locale.getLanguage(); 20197 20198 if (name) { 20199 // for DFISH-12905, pick off the part that the LDAP server automatically adds to our names in HP emails 20200 i = name.search(/\s*[,\/\(\[\{<]/); 20201 if (i !== -1) { 20202 hpSuffix = name.substring(i); 20203 hpSuffix = hpSuffix.replace(/\s+/g, ' '); // compress multiple whitespaces 20204 suffixArray = hpSuffix.split(" "); 20205 var conjunctionIndex = this._findLastConjunction(suffixArray); 20206 if (conjunctionIndex > -1) { 20207 // it's got conjunctions in it, so this is not really a suffix 20208 hpSuffix = undefined; 20209 } else { 20210 name = name.substring(0, i); 20211 } 20212 } 20213 20214 this.isAsianName = Name._isAsianName(name, currentLanguage); 20215 if (this.info.nameStyle === "asian" || this.info.order === "fmg" || this.info.order === "fgm") { 20216 info = this.isAsianName ? this.info : ilib.data.name; 20217 } else { 20218 info = this.isAsianName ? ilib.data.name : this.info; 20219 } 20220 20221 if (this.isAsianName) { 20222 // all-asian names 20223 if (this.useSpaces == false) { 20224 name = name.replace(/\s+/g, ''); // eliminate all whitespaces 20225 } 20226 parts = name.trim().split(''); 20227 } 20228 //} 20229 else { 20230 name = name.replace(/, /g, ' , '); 20231 name = name.replace(/\s+/g, ' '); // compress multiple whitespaces 20232 parts = name.trim().split(' '); 20233 } 20234 20235 // check for prefixes 20236 if (parts.length > 1) { 20237 for (i = parts.length; i > 0; i--) { 20238 prefixArray = parts.slice(0, i); 20239 prefix = prefixArray.join(this.isAsianName ? '' : ' '); 20240 prefixLower = prefix.toLowerCase(); 20241 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20242 if (ilib.isArray(this.info.prefixes) && 20243 (JSUtils.indexOf(this.info.prefixes, prefixLower) > -1 || this._isConjunction(prefixLower))) { 20244 if (this.prefix) { 20245 if (!this.isAsianName) { 20246 this.prefix += ' '; 20247 } 20248 this.prefix += prefix; 20249 } else { 20250 this.prefix = prefix; 20251 } 20252 parts = parts.slice(i); 20253 i = parts.length; 20254 } 20255 } 20256 } 20257 // check for suffixes 20258 if (parts.length > 1) { 20259 for (i = parts.length; i > 0; i--) { 20260 suffixArray = parts.slice(-i); 20261 suffix = suffixArray.join(this.isAsianName ? '' : ' '); 20262 suffixLower = suffix.toLowerCase(); 20263 suffixLower = suffixLower.replace(/[\.]/g, ''); // ignore periods 20264 if (ilib.isArray(this.info.suffixes) && JSUtils.indexOf(this.info.suffixes, suffixLower) > -1) { 20265 if (this.suffix) { 20266 if (!this.isAsianName && !isPunct(this.suffix.charAt(0))) { 20267 this.suffix = ' ' + this.suffix; 20268 } 20269 this.suffix = suffix + this.suffix; 20270 } else { 20271 this.suffix = suffix; 20272 } 20273 parts = parts.slice(0, parts.length - i); 20274 i = parts.length; 20275 } 20276 } 20277 } 20278 20279 if (hpSuffix) { 20280 this.suffix = (this.suffix && this.suffix + hpSuffix) || hpSuffix; 20281 } 20282 20283 // adjoin auxillary words to their headwords 20284 if (parts.length > 1 && !this.isAsianName) { 20285 parts = this._joinAuxillaries(parts, this.isAsianName); 20286 } 20287 20288 if (this.isAsianName) { 20289 this._parseAsianName(parts, currentLanguage); 20290 } else { 20291 this._parseWesternName(parts); 20292 } 20293 20294 this._joinNameArrays(); 20295 } 20296 }, 20297 20298 /** 20299 * @return {number} 20300 * 20301 _findSequence: function(parts, hash, isAsian) { 20302 var sequence, sequenceLower, sequenceArray, aux = [], i, ret = {}; 20303 20304 if (parts.length > 0 && hash) { 20305 //console.info("_findSequence: finding sequences"); 20306 for (var start = 0; start < parts.length-1; start++) { 20307 for ( i = parts.length; i > start; i-- ) { 20308 sequenceArray = parts.slice(start, i); 20309 sequence = sequenceArray.join(isAsian ? '' : ' '); 20310 sequenceLower = sequence.toLowerCase(); 20311 sequenceLower = sequenceLower.replace(/[,\.]/g, ''); // ignore commas and periods 20312 20313 //console.info("_findSequence: checking sequence: '" + sequenceLower + "'"); 20314 20315 if ( sequenceLower in hash ) { 20316 ret.match = sequenceArray; 20317 ret.start = start; 20318 ret.end = i; 20319 return ret; 20320 //console.info("_findSequence: Found sequence '" + sequence + "' New parts list is " + JSON.stringify(parts)); 20321 } 20322 } 20323 } 20324 } 20325 20326 return undefined; 20327 }, 20328 */ 20329 20330 /** 20331 * @protected 20332 * @param {Array} parts 20333 * @param {Array} names 20334 * @param {boolean} isAsian 20335 * @param {boolean=} noCompoundPrefix 20336 */ 20337 _findPrefix: function (parts, names, isAsian, noCompoundPrefix) { 20338 var i, prefix, prefixLower, prefixArray, aux = []; 20339 20340 if (parts.length > 0 && names) { 20341 for (i = parts.length; i > 0; i--) { 20342 prefixArray = parts.slice(0, i); 20343 prefix = prefixArray.join(isAsian ? '' : ' '); 20344 prefixLower = prefix.toLowerCase(); 20345 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20346 20347 if (prefixLower in names) { 20348 aux = aux.concat(isAsian ? prefix : prefixArray); 20349 if (noCompoundPrefix) { 20350 // don't need to parse further. Just return it as is. 20351 return aux; 20352 } 20353 parts = parts.slice(i); 20354 i = parts.length + 1; 20355 } 20356 } 20357 } 20358 20359 return aux; 20360 }, 20361 20362 /** 20363 * @protected 20364 */ 20365 _findSuffix: function (parts, names, isAsian) { 20366 var i, j, seq = ""; 20367 20368 for (i = 0; i < names.length; i++) { 20369 if (parts.length >= names[i].length) { 20370 j = 0; 20371 while (j < names[i].length && parts[parts.length - j] === names[i][names[i].length - j]) { 20372 j++; 20373 } 20374 if (j >= names[i].length) { 20375 seq = parts.slice(parts.length - j).join(isAsian ? "" : " ") + (isAsian ? "" : " ") + seq; 20376 parts = parts.slice(0, parts.length - j); 20377 i = -1; // restart the search 20378 } 20379 } 20380 } 20381 20382 this.suffix = seq; 20383 return parts; 20384 }, 20385 20386 /** 20387 * @protected 20388 * Tell whether or not the given word is a conjunction in this language. 20389 * @param {string} word the word to test 20390 * @return {boolean} true if the word is a conjunction 20391 */ 20392 _isConjunction: function _isConjunction(word) { 20393 return (this.info.conjunctions.and1 === word || 20394 this.info.conjunctions.and2 === word || 20395 this.info.conjunctions.or1 === word || 20396 this.info.conjunctions.or2 === word || 20397 ("&" === word) || 20398 ("+" === word)); 20399 }, 20400 20401 /** 20402 * Find the last instance of 'and' in the name 20403 * @protected 20404 * @param {Array.<string>} parts 20405 * @return {number} 20406 */ 20407 _findLastConjunction: function _findLastConjunction(parts) { 20408 var conjunctionIndex = -1, 20409 index, part; 20410 20411 for (index = 0; index < parts.length; index++) { 20412 part = parts[index]; 20413 if (typeof (part) === 'string') { 20414 part = part.toLowerCase(); 20415 // also recognize English 20416 if ("and" === part || "or" === part || "&" === part || "+" === part) { 20417 conjunctionIndex = index; 20418 } 20419 if (this._isConjunction(part)) { 20420 conjunctionIndex = index; 20421 } 20422 } 20423 } 20424 return conjunctionIndex; 20425 }, 20426 20427 /** 20428 * @protected 20429 * @param {Array.<string>} parts the current array of name parts 20430 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20431 * @return {Array.<string>} the remaining parts after the prefixes have been removed 20432 */ 20433 _extractPrefixes: function (parts, isAsian) { 20434 var i = this._findPrefix(parts, this.info.prefixes, isAsian); 20435 if (i > 0) { 20436 this.prefix = parts.slice(0, i).join(isAsian ? "" : " "); 20437 return parts.slice(i); 20438 } 20439 // prefixes not found, so just return the array unmodified 20440 return parts; 20441 }, 20442 20443 /** 20444 * @protected 20445 * @param {Array.<string>} parts the current array of name parts 20446 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20447 * @return {Array.<string>} the remaining parts after the suffices have been removed 20448 */ 20449 _extractSuffixes: function (parts, isAsian) { 20450 var i = this._findSuffix(parts, this.info.suffixes, isAsian); 20451 if (i > 0) { 20452 this.suffix = parts.slice(i).join(isAsian ? "" : " "); 20453 return parts.slice(0, i); 20454 } 20455 // suffices not found, so just return the array unmodified 20456 return parts; 20457 }, 20458 20459 /** 20460 * Adjoin auxillary words to their head words. 20461 * @protected 20462 * @param {Array.<string>} parts the current array of name parts 20463 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20464 * @return {Array.<string>} the parts after the auxillary words have been plucked onto their head word 20465 */ 20466 _joinAuxillaries: function (parts, isAsian) { 20467 var start, i, prefixArray, prefix, prefixLower; 20468 20469 if (this.info.auxillaries && (parts.length > 2 || this.prefix)) { 20470 for (start = 0; start < parts.length - 1; start++) { 20471 for (i = parts.length; i > start; i--) { 20472 prefixArray = parts.slice(start, i); 20473 prefix = prefixArray.join(' '); 20474 prefixLower = prefix.toLowerCase(); 20475 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20476 20477 if (prefixLower in this.info.auxillaries) { 20478 parts.splice(start, i + 1 - start, prefixArray.concat(parts[i])); 20479 i = start; 20480 } 20481 } 20482 } 20483 } 20484 20485 return parts; 20486 }, 20487 20488 /** 20489 * Recursively join an array or string into a long string. 20490 * @protected 20491 */ 20492 _joinArrayOrString: function _joinArrayOrString(part) { 20493 var i; 20494 if (typeof (part) === 'object') { 20495 for (i = 0; i < part.length; i++) { 20496 part[i] = this._joinArrayOrString(part[i]); 20497 } 20498 var ret = ""; 20499 part.forEach(function (segment) { 20500 if (ret.length > 0 && !isPunct(segment.charAt(0))) { 20501 ret += ' '; 20502 } 20503 ret += segment; 20504 }); 20505 20506 return ret; 20507 } 20508 20509 return part; 20510 }, 20511 20512 /** 20513 * @protected 20514 */ 20515 _joinNameArrays: function _joinNameArrays() { 20516 var prop; 20517 for (prop in this) { 20518 20519 if (this[prop] !== undefined && typeof (this[prop]) === 'object' && ilib.isArray(this[prop])) { 20520 20521 this[prop] = this._joinArrayOrString(this[prop]); 20522 } 20523 } 20524 }, 20525 20526 /** 20527 * @protected 20528 */ 20529 _parseAsianName: function (parts, language) { 20530 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 20531 var tempFullName = parts.join(''); 20532 20533 if (familyNameArray && familyNameArray.length > 0) { 20534 this.familyName = familyNameArray.join(''); 20535 this.givenName = parts.slice(this.familyName.length).join(''); 20536 20537 //Overide parsing rules if spaces are found in korean 20538 if (language === "ko" && tempFullName.search(/\s*[/\s]/) > -1 && !this.suffix) { 20539 this._parseKoreanName(tempFullName); 20540 } 20541 } else if (this.locale.getLanguage() === "ja") { 20542 this._parseJapaneseName(parts); 20543 } else if (this.suffix || this.prefix) { 20544 this.familyName = parts.join(''); 20545 } else { 20546 this.givenName = parts.join(''); 20547 } 20548 }, 20549 20550 /** 20551 * @protected 20552 */ 20553 _parseKoreanName: function (name) { 20554 var tempName = name; 20555 20556 var spaceSplit = tempName.split(" "); 20557 var spceCount = spaceSplit.length; 20558 var fistSpaceIndex = tempName.indexOf(" "); 20559 var lastSpaceIndex = tempName.lastIndexOf(" "); 20560 20561 if (spceCount === 2) { 20562 this.familyName = spaceSplit[0]; 20563 this.givenName = tempName.slice(fistSpaceIndex, tempName.length); 20564 } else { 20565 this.familyName = spaceSplit[0]; 20566 this.middleName = tempName.slice(fistSpaceIndex, lastSpaceIndex); 20567 this.givenName = tempName.slice(lastSpaceIndex, tempName.length); 20568 } 20569 20570 }, 20571 20572 /** 20573 * @protected 20574 */ 20575 _parseJapaneseName: function (parts) { 20576 if (this.suffix && this.suffix.length > 1 && this.info.honorifics.indexOf(this.suffix)>-1) { 20577 if (parts.length === 1) { 20578 if (CType.withinRange(parts[0], "cjk")) { 20579 this.familyName = parts[0]; 20580 } else { 20581 this.givenName = parts[0]; 20582 } 20583 return; 20584 } else if (parts.length === 2) { 20585 this.familyName = parts.slice(0,parts.length).join("") 20586 return; 20587 } 20588 } 20589 if (parts.length > 1) { 20590 var fn = ""; 20591 for (var i = 0; i < parts.length; i++) { 20592 if (CType.withinRange(parts[i], "cjk")) { 20593 fn += parts[i]; 20594 } else if (fn.length > 1 && CType.withinRange(parts[i], "hiragana")) { 20595 this.familyName = fn; 20596 this.givenName = parts.slice(i,parts.length).join(""); 20597 return; 20598 } else { 20599 break; 20600 } 20601 } 20602 } 20603 if (parts.length === 1) { 20604 this.familyName = parts[0]; 20605 } else if (parts.length === 2) { 20606 this.familyName = parts[0]; 20607 this.givenName = parts[1]; 20608 } else if (parts.length === 3) { 20609 this.familyName = parts[0]; 20610 this.givenName = parts.slice(1,parts.length).join(""); 20611 } else if (parts.length > 3) { 20612 this.familyName = parts.slice(0,2).join("") 20613 this.givenName = parts.slice(2,parts.length).join(""); 20614 } 20615 }, 20616 20617 /** 20618 * @protected 20619 */ 20620 _parseSpanishName: function (parts) { 20621 var conjunctionIndex; 20622 20623 if (parts.length === 1) { 20624 if (this.prefix || typeof (parts[0]) === 'object') { 20625 this.familyName = parts[0]; 20626 } else { 20627 this.givenName = parts[0]; 20628 } 20629 } else if (parts.length === 2) { 20630 // we do G F 20631 this.givenName = parts[0]; 20632 this.familyName = parts[1]; 20633 } else if (parts.length === 3) { 20634 conjunctionIndex = this._findLastConjunction(parts); 20635 // if there's an 'and' in the middle spot, put everything in the first name 20636 if (conjunctionIndex === 1) { 20637 this.givenName = parts; 20638 } else { 20639 // else, do G F F 20640 this.givenName = parts[0]; 20641 this.familyName = parts.slice(1); 20642 } 20643 } else if (parts.length > 3) { 20644 //there are at least 4 parts to this name 20645 20646 conjunctionIndex = this._findLastConjunction(parts); 20647 ////console.log("@@@@@@@@@@@@@@@@"+conjunctionIndex) 20648 if (conjunctionIndex > 0) { 20649 // if there's a conjunction that's not the first token, put everything up to and 20650 // including the token after it into the first name, the last 2 tokens into 20651 // the family name (if they exist) and everything else in to the middle name 20652 // 0 1 2 3 4 5 20653 // G A G 20654 // G A G F 20655 // G G A G 20656 // G A G F F 20657 // G G A G F 20658 // G G G A G 20659 // G A G M F F 20660 // G G A G F F 20661 // G G G A G F 20662 // G G G G A G 20663 this.givenName = parts.splice(0, conjunctionIndex + 2); 20664 if (parts.length > 1) { 20665 this.familyName = parts.splice(parts.length - 2, 2); 20666 if (parts.length > 0) { 20667 this.middleName = parts; 20668 } 20669 } else if (parts.length === 1) { 20670 this.familyName = parts[0]; 20671 } 20672 } else { 20673 this.givenName = parts.splice(0, 1); 20674 this.familyName = parts.splice(parts.length - 2, 2); 20675 this.middleName = parts; 20676 } 20677 } 20678 }, 20679 20680 /** 20681 * @protected 20682 */ 20683 _parseIndonesianName: function (parts) { 20684 var conjunctionIndex; 20685 20686 if (parts.length === 1) { 20687 //if (this.prefix || typeof(parts[0]) === 'object') { 20688 //this.familyName = parts[0]; 20689 //} else { 20690 this.givenName = parts[0]; 20691 //} 20692 //} else if (parts.length === 2) { 20693 // we do G F 20694 //this.givenName = parts[0]; 20695 //this.familyName = parts[1]; 20696 } else if (parts.length >= 2) { 20697 //there are at least 3 parts to this name 20698 20699 conjunctionIndex = this._findLastConjunction(parts); 20700 if (conjunctionIndex > 0) { 20701 // if there's a conjunction that's not the first token, put everything up to and 20702 // including the token after it into the first name, the last 2 tokens into 20703 // the family name (if they exist) and everything else in to the middle name 20704 // 0 1 2 3 4 5 20705 // G A G 20706 // G A G F 20707 // G G A G 20708 // G A G F F 20709 // G G A G F 20710 // G G G A G 20711 // G A G M F F 20712 // G G A G F F 20713 // G G G A G F 20714 // G G G G A G 20715 this.givenName = parts.splice(0, conjunctionIndex + 2); 20716 if (parts.length > 1) { 20717 //this.familyName = parts.splice(parts.length-2, 2); 20718 //if ( parts.length > 0 ) { 20719 this.middleName = parts; 20720 } 20721 //} else if (parts.length === 1) { 20722 // this.familyName = parts[0]; 20723 //} 20724 } else { 20725 this.givenName = parts.splice(0, 1); 20726 //this.familyName = parts.splice(parts.length-2, 2); 20727 this.middleName = parts; 20728 } 20729 } 20730 }, 20731 20732 /** 20733 * @protected 20734 */ 20735 _parseGenericWesternName: function (parts) { 20736 /* Western names are parsed as follows, and rules are applied in this 20737 * order: 20738 * 20739 * G 20740 * G F 20741 * G M F 20742 * G M M F 20743 * P F 20744 * P G F 20745 */ 20746 var conjunctionIndex; 20747 20748 if (parts.length === 1) { 20749 if (this.prefix || typeof (parts[0]) === 'object') { 20750 // already has a prefix, so assume it goes with the family name like "Dr. Roberts" or 20751 // it is a name with auxillaries, which is almost always a family name 20752 this.familyName = parts[0]; 20753 } else { 20754 this.givenName = parts[0]; 20755 } 20756 } else if (parts.length === 2) { 20757 // we do G F 20758 if (this.info.order == 'fgm') { 20759 this.givenName = parts[1]; 20760 this.familyName = parts[0]; 20761 } else if (this.info.order == "gmf" || typeof (this.info.order) == 'undefined') { 20762 this.givenName = parts[0]; 20763 this.familyName = parts[1]; 20764 } 20765 } else if (parts.length >= 3) { 20766 //find the first instance of 'and' in the name 20767 conjunctionIndex = this._findLastConjunction(parts); 20768 20769 if (conjunctionIndex > 0) { 20770 // if there's a conjunction that's not the first token, put everything up to and 20771 // including the token after it into the first name, the last token into 20772 // the family name (if it exists) and everything else in to the middle name 20773 // 0 1 2 3 4 5 20774 // G A G M M F 20775 // G G A G M F 20776 // G G G A G F 20777 // G G G G A G 20778 //if(this.order == "gmf") { 20779 this.givenName = parts.slice(0, conjunctionIndex + 2); 20780 20781 if (conjunctionIndex + 1 < parts.length - 1) { 20782 this.familyName = parts.splice(parts.length - 1, 1); 20783 ////console.log(this.familyName); 20784 if (conjunctionIndex + 2 < parts.length - 1) { 20785 this.middleName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 20786 } 20787 } else if (this.order == "fgm") { 20788 this.familyName = parts.slice(0, conjunctionIndex + 2); 20789 if (conjunctionIndex + 1 < parts.length - 1) { 20790 this.middleName = parts.splice(parts.length - 1, 1); 20791 if (conjunctionIndex + 2 < parts.length - 1) { 20792 this.givenName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 20793 } 20794 } 20795 } 20796 } else { 20797 this.givenName = parts[0]; 20798 20799 this.middleName = parts.slice(1, parts.length - 1); 20800 20801 this.familyName = parts[parts.length - 1]; 20802 } 20803 } 20804 }, 20805 20806 /** 20807 * parse patrinomic name from the russian names 20808 * @protected 20809 * @param {Array.<string>} parts the current array of name parts 20810 * @return number index of the part which contains patronymic name 20811 */ 20812 _findPatronymicName: function(parts) { 20813 var index, part; 20814 for (index = 0; index < parts.length; index++) { 20815 part = parts[index]; 20816 if (typeof (part) === 'string') { 20817 part = part.toLowerCase(); 20818 20819 var subLength = this.info.patronymicName.length; 20820 while(subLength--) { 20821 if(part.indexOf(this.info.patronymicName[subLength])!== -1 ) 20822 return index; 20823 } 20824 } 20825 } 20826 return -1; 20827 }, 20828 20829 /** 20830 * find if the given part is patronymic name 20831 * 20832 * @protected 20833 * @param {string} part string from name parts @ 20834 * @return number index of the part which contains familyName 20835 */ 20836 _isPatronymicName: function(part) { 20837 var pName; 20838 if ( typeof (part) === 'string') { 20839 pName = part.toLowerCase(); 20840 20841 var subLength = this.info.patronymicName.length; 20842 while (subLength--) { 20843 if (pName.indexOf(this.info.patronymicName[subLength]) !== -1) 20844 return true; 20845 } 20846 } 20847 return false; 20848 }, 20849 20850 /** 20851 * find family name from the russian name 20852 * 20853 * @protected 20854 * @param {Array.<string>} parts the current array of name parts 20855 * @return boolean true if patronymic, false otherwise 20856 */ 20857 _findFamilyName: function(parts) { 20858 var index, part, substring; 20859 for (index = 0; index < parts.length; index++) { 20860 part = parts[index]; 20861 20862 if ( typeof (part) === 'string') { 20863 part = part.toLowerCase(); 20864 var length = part.length - 1; 20865 20866 if (this.info.familyName.indexOf(part) !== -1) { 20867 return index; 20868 } else if (part[length] === 'в' || part[length] === 'н' || 20869 part[length] === 'й') { 20870 substring = part.slice(0, -1); 20871 if (this.info.familyName.indexOf(substring) !== -1) { 20872 return index; 20873 } 20874 } else if ((part[length - 1] === 'в' && part[length] === 'а') || 20875 (part[length - 1] === 'н' && part[length] === 'а') || 20876 (part[length - 1] === 'а' && part[length] === 'я')) { 20877 substring = part.slice(0, -2); 20878 if (this.info.familyName.indexOf(substring) !== -1) { 20879 return index; 20880 } 20881 } 20882 } 20883 } 20884 return -1; 20885 }, 20886 20887 /** 20888 * parse russian name 20889 * 20890 * @protected 20891 * @param {Array.<string>} parts the current array of name parts 20892 * @return 20893 */ 20894 _parseRussianName: function(parts) { 20895 var conjunctionIndex, familyIndex = -1; 20896 20897 if (parts.length === 1) { 20898 if (this.prefix || typeof (parts[0]) === 'object') { 20899 // already has a prefix, so assume it goes with the family name 20900 // like "Dr. Roberts" or 20901 // it is a name with auxillaries, which is almost always a 20902 // family name 20903 this.familyName = parts[0]; 20904 } else { 20905 this.givenName = parts[0]; 20906 } 20907 } else if (parts.length === 2) { 20908 // we do G F 20909 if (this.info.order === 'fgm') { 20910 this.givenName = parts[1]; 20911 this.familyName = parts[0]; 20912 } else if (this.info.order === "gmf") { 20913 this.givenName = parts[0]; 20914 this.familyName = parts[1]; 20915 } else if ( typeof (this.info.order) === 'undefined') { 20916 if (this._isPatronymicName(parts[1]) === true) { 20917 this.middleName = parts[1]; 20918 this.givenName = parts[0]; 20919 } else if ((familyIndex = this._findFamilyName(parts)) !== -1) { 20920 if (familyIndex === 1) { 20921 this.givenName = parts[0]; 20922 this.familyName = parts[1]; 20923 } else { 20924 this.familyName = parts[0]; 20925 this.givenName = parts[1]; 20926 } 20927 20928 } else { 20929 this.givenName = parts[0]; 20930 this.familyName = parts[1]; 20931 } 20932 20933 } 20934 } else if (parts.length >= 3) { 20935 // find the first instance of 'and' in the name 20936 conjunctionIndex = this._findLastConjunction(parts); 20937 var patronymicNameIndex = this._findPatronymicName(parts); 20938 if (conjunctionIndex > 0) { 20939 // if there's a conjunction that's not the first token, put 20940 // everything up to and 20941 // including the token after it into the first name, the last 20942 // token into 20943 // the family name (if it exists) and everything else in to the 20944 // middle name 20945 // 0 1 2 3 4 5 20946 // G A G M M F 20947 // G G A G M F 20948 // G G G A G F 20949 // G G G G A G 20950 // if(this.order == "gmf") { 20951 this.givenName = parts.slice(0, conjunctionIndex + 2); 20952 20953 if (conjunctionIndex + 1 < parts.length - 1) { 20954 this.familyName = parts.splice(parts.length - 1, 1); 20955 // //console.log(this.familyName); 20956 if (conjunctionIndex + 2 < parts.length - 1) { 20957 this.middleName = parts.slice(conjunctionIndex + 2, 20958 parts.length - conjunctionIndex - 3); 20959 } 20960 } else if (this.order == "fgm") { 20961 this.familyName = parts.slice(0, conjunctionIndex + 2); 20962 if (conjunctionIndex + 1 < parts.length - 1) { 20963 this.middleName = parts.splice(parts.length - 1, 1); 20964 if (conjunctionIndex + 2 < parts.length - 1) { 20965 this.givenName = parts.slice(conjunctionIndex + 2, 20966 parts.length - conjunctionIndex - 3); 20967 } 20968 } 20969 } 20970 } else if (patronymicNameIndex !== -1) { 20971 this.middleName = parts[patronymicNameIndex]; 20972 20973 if (patronymicNameIndex === (parts.length - 1)) { 20974 this.familyName = parts[0]; 20975 this.givenName = parts.slice(1, patronymicNameIndex); 20976 } else { 20977 this.givenName = parts.slice(0, patronymicNameIndex); 20978 20979 this.familyName = parts[parts.length - 1]; 20980 } 20981 } else { 20982 this.givenName = parts[0]; 20983 20984 this.middleName = parts.slice(1, parts.length - 1); 20985 20986 this.familyName = parts[parts.length - 1]; 20987 } 20988 } 20989 }, 20990 20991 20992 /** 20993 * @protected 20994 */ 20995 _parseWesternName: function (parts) { 20996 20997 if (this.locale.getLanguage() === "es" || this.locale.getLanguage() === "pt") { 20998 // in spain and mexico and portugal, we parse names differently than in the rest of the world 20999 // because of the double family names 21000 this._parseSpanishName(parts); 21001 } else if (this.locale.getLanguage() === "ru") { 21002 /* 21003 * In Russian, names can be given equally validly as given-family 21004 * or family-given. Use the value of the "order" property of the 21005 * constructor options to give the default when the order is ambiguous. 21006 */ 21007 this._parseRussianName(parts); 21008 } else if (this.locale.getLanguage() === "id") { 21009 // in indonesia, we parse names differently than in the rest of the world 21010 // because names don't have family names usually. 21011 this._parseIndonesianName(parts); 21012 } else { 21013 this._parseGenericWesternName(parts); 21014 } 21015 }, 21016 21017 /** 21018 * When sorting names with auxiliary words (like "van der" or "de los"), determine 21019 * which is the "head word" and return a string that can be easily sorted by head 21020 * word. In English, names are always sorted by initial characters. In places like 21021 * the Netherlands or Germany, family names are sorted by the head word of a list 21022 * of names rather than the first element of that name. 21023 * @return {string|undefined} a string containing the family name[s] to be used for sorting 21024 * in the current locale, or undefined if there is no family name in this object 21025 */ 21026 getSortFamilyName: function () { 21027 var name, 21028 auxillaries, 21029 auxString, 21030 parts, 21031 i; 21032 21033 // no name to sort by 21034 if (!this.familyName) { 21035 return undefined; 21036 } 21037 21038 // first break the name into parts 21039 if (this.info) { 21040 if (this.info.sortByHeadWord) { 21041 if (typeof (this.familyName) === 'string') { 21042 name = this.familyName.replace(/\s+/g, ' '); // compress multiple whitespaces 21043 parts = name.trim().split(' '); 21044 } else { 21045 // already split 21046 parts = this.familyName; 21047 } 21048 21049 auxillaries = this._findPrefix(parts, this.info.auxillaries, false); 21050 if (auxillaries && auxillaries.length > 0) { 21051 if (typeof (this.familyName) === 'string') { 21052 auxString = auxillaries.join(' '); 21053 name = this.familyName.substring(auxString.length + 1) + ', ' + auxString; 21054 } else { 21055 name = parts.slice(auxillaries.length).join(' ') + 21056 ', ' + 21057 parts.slice(0, auxillaries.length).join(' '); 21058 } 21059 } 21060 } else if (this.info.knownFamilyNames && this.familyName) { 21061 parts = this.familyName.split(''); 21062 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 21063 name = ""; 21064 for (i = 0; i < familyNameArray.length; i++) { 21065 name += (this.info.knownFamilyNames[familyNameArray[i]] || ""); 21066 } 21067 } 21068 } 21069 21070 return name || this.familyName; 21071 }, 21072 21073 getHeadFamilyName: function () {}, 21074 21075 /** 21076 * @protected 21077 * Return a shallow copy of the current instance. 21078 */ 21079 clone: function () { 21080 return new Name(this); 21081 } 21082 }; 21083 21084 21085 /*< NameFmt.js */ 21086 /* 21087 * NameFmt.js - Format person names for display 21088 * 21089 * Copyright © 2013-2015, JEDLSoft 21090 * 21091 * Licensed under the Apache License, Version 2.0 (the "License"); 21092 * you may not use this file except in compliance with the License. 21093 * You may obtain a copy of the License at 21094 * 21095 * http://www.apache.org/licenses/LICENSE-2.0 21096 * 21097 * Unless required by applicable law or agreed to in writing, software 21098 * distributed under the License is distributed on an "AS IS" BASIS, 21099 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21100 * 21101 * See the License for the specific language governing permissions and 21102 * limitations under the License. 21103 */ 21104 21105 /* !depends 21106 ilib.js 21107 Locale.js 21108 IString.js 21109 Name.js 21110 isPunct.js 21111 Utils.js 21112 */ 21113 21114 // !data name 21115 21116 21117 21118 21119 /** 21120 * @class 21121 * Creates a formatter that can format person name instances (Name) for display to 21122 * a user. The options may contain the following properties: 21123 * 21124 * <ul> 21125 * <li><i>locale</i> - Use the conventions of the given locale to construct the name format. 21126 * <li><i>style</i> - Format the name with the given style. The value of this property 21127 * should be one of the following strings: 21128 * <ul> 21129 * <li><i>short</i> - Format a short name with just the given and family names. 21130 * <li><i>medium</i> - Format a medium-length name with the given, middle, and family names. 21131 * <li><i>long</i> - Format a long name with all names available in the given name object, including 21132 * prefixes. 21133 * <li><i>full</i> - Format a long name with all names available in the given name object, including 21134 * prefixes and suffixes. 21135 * </ul> 21136 * <li><i>components</i> - Format the name with the given components in the correct 21137 * order for those components. Components are encoded as a string of letters representing 21138 * the desired components: 21139 * <ul> 21140 * <li><i>p</i> - prefixes 21141 * <li><i>g</i> - given name 21142 * <li><i>m</i> - middle names 21143 * <li><i>f</i> - family name 21144 * <li><i>s</i> - suffixes 21145 * </ul> 21146 * <p> 21147 * 21148 * For example, the string "pf" would mean to only format any prefixes and family names 21149 * together and leave out all the other parts of the name.<p> 21150 * 21151 * The components can be listed in any order in the string. The <i>components</i> option 21152 * overrides the <i>style</i> option if both are specified. 21153 * 21154 * <li>onLoad - a callback function to call when the locale info object is fully 21155 * loaded. When the onLoad option is given, the localeinfo object will attempt to 21156 * load any missing locale data using the ilib loader callback. 21157 * When the constructor is done (even if the data is already preassembled), the 21158 * onLoad function is called with the current instance as a parameter, so this 21159 * callback can be used with preassembled or dynamic loading or a mix of the two. 21160 * 21161 * <li>sync - tell whether to load any missing locale data synchronously or 21162 * asynchronously. If this option is given as "false", then the "onLoad" 21163 * callback must be given, as the instance returned from this constructor will 21164 * not be usable for a while. 21165 * 21166 * <li><i>loadParams</i> - an object containing parameters to pass to the 21167 * loader callback function when locale data is missing. The parameters are not 21168 * interpretted or modified in any way. They are simply passed along. The object 21169 * may contain any property/value pairs as long as the calling code is in 21170 * agreement with the loader callback function as to what those parameters mean. 21171 * </ul> 21172 * 21173 * Formatting names is a locale-dependent function, as the order of the components 21174 * depends on the locale. The following explains some of the details:<p> 21175 * 21176 * <ul> 21177 * <li>In Western countries, the given name comes first, followed by a space, followed 21178 * by the family name. In Asian countries, the family name comes first, followed immediately 21179 * by the given name with no space. But, that format is only used with Asian names written 21180 * in ideographic characters. In Asian countries, especially ones where both an Asian and 21181 * a Western language are used (Hong Kong, Singapore, etc.), the convention is often to 21182 * follow the language of the name. That is, Asian names are written in Asian style, and 21183 * Western names are written in Western style. This class follows that convention as 21184 * well. 21185 * <li>In other Asian countries, Asian names 21186 * written in Latin script are written with Asian ordering. eg. "Xu Ping-an" instead 21187 * of the more Western order "Ping-an Xu", as the order is thought to go with the style 21188 * that is appropriate for the name rather than the style for the language being written. 21189 * <li>In some Spanish speaking countries, people often take both their maternal and 21190 * paternal last names as their own family name. When formatting a short or medium style 21191 * of that family name, only the paternal name is used. In the long style, all the names 21192 * are used. eg. "Juan Julio Raul Lopez Ortiz" took the name "Lopez" from his father and 21193 * the name "Ortiz" from his mother. His family name would be "Lopez Ortiz". The formatted 21194 * short style of his name would be simply "Juan Lopez" which only uses his paternal 21195 * family name of "Lopez". 21196 * <li>In many Western languages, it is common to use auxillary words in family names. For 21197 * example, the family name of "Ludwig von Beethoven" in German is "von Beethoven", not 21198 * "Beethoven". This class ensures that the family name is formatted correctly with 21199 * all auxillary words. 21200 * </ul> 21201 * 21202 * 21203 * @constructor 21204 * @param {Object} options A set of options that govern how the formatter will behave 21205 */ 21206 var NameFmt = function(options) { 21207 var sync = true; 21208 21209 this.style = "short"; 21210 this.loadParams = {}; 21211 21212 if (options) { 21213 if (options.locale) { 21214 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21215 } 21216 21217 if (options.style) { 21218 this.style = options.style; 21219 } 21220 21221 if (options.components) { 21222 this.components = options.components; 21223 } 21224 21225 if (typeof(options.sync) !== 'undefined') { 21226 sync = (options.sync == true); 21227 } 21228 21229 if (typeof(options.loadParams) !== 'undefined') { 21230 this.loadParams = options.loadParams; 21231 } 21232 } 21233 21234 // set up defaults in case we need them 21235 this.defaultEuroTemplate = new IString("{prefix} {givenName} {middleName} {familyName}{suffix}"); 21236 this.defaultAsianTemplate = new IString("{prefix}{familyName}{givenName}{middleName}{suffix}"); 21237 this.useFirstFamilyName = false; 21238 21239 switch (this.style) { 21240 default: 21241 case "s": 21242 case "short": 21243 this.style = "short"; 21244 break; 21245 case "m": 21246 case "medium": 21247 this.style = "medium"; 21248 break; 21249 case "l": 21250 case "long": 21251 this.style = "long"; 21252 break; 21253 case "f": 21254 case "full": 21255 this.style = "full"; 21256 break; 21257 } 21258 21259 if (!Name.cache) { 21260 Name.cache = {}; 21261 } 21262 21263 this.locale = this.locale || new Locale(); 21264 21265 isPunct._init(sync, this.loadParams, ilib.bind(this, function() { 21266 Utils.loadData({ 21267 object: Name, 21268 locale: this.locale, 21269 name: "name.json", 21270 sync: sync, 21271 loadParams: this.loadParams, 21272 callback: ilib.bind(this, function (info) { 21273 if (!info) { 21274 info = Name.defaultInfo; 21275 var spec = this.locale.getSpec().replace(/-/g, "_"); 21276 Name.cache[spec] = info; 21277 } 21278 this.info = info; 21279 this._init(); 21280 if (options && typeof(options.onLoad) === 'function') { 21281 options.onLoad(this); 21282 } 21283 }) 21284 }); 21285 })); 21286 }; 21287 21288 NameFmt.prototype = { 21289 /** 21290 * @protected 21291 */ 21292 _init: function() { 21293 if (this.components) { 21294 var valids = {"p":1,"g":1,"m":1,"f":1,"s":1}, 21295 arr = this.components.split(""); 21296 this.comps = {}; 21297 for (var i = 0; i < arr.length; i++) { 21298 if (valids[arr[i].toLowerCase()]) { 21299 this.comps[arr[i].toLowerCase()] = true; 21300 } 21301 } 21302 } else { 21303 this.comps = this.info.components[this.style]; 21304 } 21305 21306 this.template = new IString(this.info.format); 21307 21308 if (this.locale.language === "es" && (this.style !== "long" && this.style !== "full")) { 21309 this.useFirstFamilyName = true; // in spanish, they have 2 family names, the maternal and paternal 21310 } 21311 21312 this.isAsianLocale = (this.info.nameStyle === "asian"); 21313 }, 21314 21315 /** 21316 * adjoin auxillary words to their head words 21317 * @protected 21318 */ 21319 _adjoinAuxillaries: function (parts, namePrefix) { 21320 var start, i, prefixArray, prefix, prefixLower; 21321 21322 //console.info("_adjoinAuxillaries: finding and adjoining aux words in " + parts.join(' ')); 21323 21324 if ( this.info.auxillaries && (parts.length > 2 || namePrefix) ) { 21325 for ( start = 0; start < parts.length-1; start++ ) { 21326 for ( i = parts.length; i > start; i-- ) { 21327 prefixArray = parts.slice(start, i); 21328 prefix = prefixArray.join(' '); 21329 prefixLower = prefix.toLowerCase(); 21330 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 21331 21332 //console.info("_adjoinAuxillaries: checking aux prefix: '" + prefixLower + "' which is " + start + " to " + i); 21333 21334 if ( prefixLower in this.info.auxillaries ) { 21335 //console.info("Found! Old parts list is " + JSON.stringify(parts)); 21336 parts.splice(start, i+1-start, prefixArray.concat(parts[i])); 21337 //console.info("_adjoinAuxillaries: Found! New parts list is " + JSON.stringify(parts)); 21338 i = start; 21339 } 21340 } 21341 } 21342 } 21343 21344 //console.info("_adjoinAuxillaries: done. Result is " + JSON.stringify(parts)); 21345 21346 return parts; 21347 }, 21348 21349 /** 21350 * Return the locale for this formatter instance. 21351 * @return {Locale} the locale instance for this formatter 21352 */ 21353 getLocale: function () { 21354 return this.locale; 21355 }, 21356 21357 /** 21358 * Return the style of names returned by this formatter 21359 * @return {string} the style of names returned by this formatter 21360 */ 21361 getStyle: function () { 21362 return this.style; 21363 }, 21364 21365 /** 21366 * Return the list of components used to format names in this formatter 21367 * @return {string} the list of components 21368 */ 21369 getComponents: function () { 21370 return this.components; 21371 }, 21372 21373 /** 21374 * Format the name for display in the current locale with the options set up 21375 * in the constructor of this formatter instance.<p> 21376 * 21377 * If the name does not contain all the parts required for the style, those parts 21378 * will be left blank.<p> 21379 * 21380 * There are two basic styles of formatting: European, and Asian. If this formatter object 21381 * is set for European style, but an Asian name is passed to the format method, then this 21382 * method will format the Asian name with a generic Asian template. Similarly, if the 21383 * formatter is set for an Asian style, and a European name is passed to the format method, 21384 * the formatter will use a generic European template.<p> 21385 * 21386 * This means it is always safe to format any name with a formatter for any locale. You should 21387 * always get something at least reasonable as output.<p> 21388 * 21389 * @param {Name} name the name to format 21390 * @return {string|undefined} the name formatted according to the style of this formatter instance 21391 */ 21392 format: function(name) { 21393 var formatted, temp, modified, isAsianName; 21394 var currentLanguage = this.locale.getLanguage(); 21395 21396 if (!name || typeof(name) !== 'object') { 21397 return undefined; 21398 } 21399 21400 if ((typeof(name.isAsianName) === 'boolean' && !name.isAsianName) || 21401 Name._isEuroName([name.givenName, name.middleName, name.familyName].join(""), currentLanguage)) { 21402 isAsianName = false; // this is a euro name, even if the locale is asian 21403 modified = name.clone(); 21404 21405 // handle the case where there is no space if there is punctuation in the suffix like ", Phd". 21406 // Otherwise, put a space in to transform "PhD" to " PhD" 21407 /* 21408 console.log("suffix is " + modified.suffix); 21409 if ( modified.suffix ) { 21410 console.log("first char is " + modified.suffix.charAt(0)); 21411 console.log("isPunct(modified.suffix.charAt(0)) is " + isPunct(modified.suffix.charAt(0))); 21412 } 21413 */ 21414 if (modified.suffix && isPunct(modified.suffix.charAt(0)) === false) { 21415 modified.suffix = ' ' + modified.suffix; 21416 } 21417 21418 if (this.useFirstFamilyName && name.familyName) { 21419 var familyNameParts = modified.familyName.trim().split(' '); 21420 if (familyNameParts.length > 1) { 21421 familyNameParts = this._adjoinAuxillaries(familyNameParts, name.prefix); 21422 } //in spain and mexico, we parse names differently than in the rest of the world 21423 21424 modified.familyName = familyNameParts[0]; 21425 } 21426 21427 modified._joinNameArrays(); 21428 } else { 21429 isAsianName = true; 21430 modified = name; 21431 if (modified.suffix && currentLanguage === "ko" && this.info.honorifics.indexOf(name.suffix) == -1) { 21432 modified.suffix = ' ' + modified.suffix; 21433 } 21434 } 21435 21436 if (!this.template || isAsianName !== this.isAsianLocale) { 21437 temp = isAsianName ? this.defaultAsianTemplate : this.defaultEuroTemplate; 21438 } else { 21439 temp = this.template; 21440 } 21441 21442 var parts = { 21443 prefix: this.comps["p"] && modified.prefix || "", 21444 givenName: this.comps["g"] && modified.givenName || "", 21445 middleName: this.comps["m"] && modified.middleName || "", 21446 familyName: this.comps["f"] && modified.familyName || "", 21447 suffix: this.comps["s"] && modified.suffix || "" 21448 }; 21449 21450 formatted = temp.format(parts); 21451 return formatted.replace(/\s+/g, ' ').trim(); 21452 } 21453 }; 21454 21455 21456 /*< Address.js */ 21457 /* 21458 * Address.js - Represent a mailing address 21459 * 21460 * Copyright © 2013-2015, JEDLSoft 21461 * 21462 * Licensed under the Apache License, Version 2.0 (the "License"); 21463 * you may not use this file except in compliance with the License. 21464 * You may obtain a copy of the License at 21465 * 21466 * http://www.apache.org/licenses/LICENSE-2.0 21467 * 21468 * Unless required by applicable law or agreed to in writing, software 21469 * distributed under the License is distributed on an "AS IS" BASIS, 21470 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21471 * 21472 * See the License for the specific language governing permissions and 21473 * limitations under the License. 21474 */ 21475 21476 /*globals console RegExp */ 21477 21478 /* !depends 21479 ilib.js 21480 Utils.js 21481 JSUtils.js 21482 Locale.js 21483 isIdeo.js 21484 isAscii.js 21485 isDigit.js 21486 IString.js 21487 */ 21488 21489 // !data address countries nativecountries ctrynames 21490 21491 21492 /** 21493 * @class 21494 * Create a new Address instance and parse a physical address.<p> 21495 * 21496 * This function parses a physical address written in a free-form string. 21497 * It returns an object with a number of properties from the list below 21498 * that it may have extracted from that address.<p> 21499 * 21500 * The following is a list of properties that the algorithm will return:<p> 21501 * 21502 * <ul> 21503 * <li><i>streetAddress</i>: The street address, including house numbers and all. 21504 * <li><i>locality</i>: The locality of this address (usually a city or town). 21505 * <li><i>region</i>: The region where the locality is located. In the US, this 21506 * corresponds to states. In other countries, this may be provinces, 21507 * cantons, prefectures, etc. In some smaller countries, there are no 21508 * such divisions. 21509 * <li><i>postalCode</i>: Country-specific code for expediting mail. In the US, 21510 * this is the zip code. 21511 * <li><i>country</i>: The country of the address. 21512 * <li><i>countryCode</i>: The ISO 3166 2-letter region code for the destination 21513 * country in this address. 21514 * </ul> 21515 * 21516 * The above properties will not necessarily appear in the instance. For 21517 * any individual property, if the free-form address does not contain 21518 * that property or it cannot be parsed out, the it is left out.<p> 21519 * 21520 * The options parameter may contain any of the following properties: 21521 * 21522 * <ul> 21523 * <li><i>locale</i> - locale or localeSpec to use to parse the address. If not 21524 * specified, this function will use the current ilib locale 21525 * 21526 * <li><i>onLoad</i> - a callback function to call when the address info for the 21527 * locale is fully loaded and the address has been parsed. When the onLoad 21528 * option is given, the address object 21529 * will attempt to load any missing locale data using the ilib loader callback. 21530 * When the constructor is done (even if the data is already preassembled), the 21531 * onLoad function is called with the current instance as a parameter, so this 21532 * callback can be used with preassembled or dynamic loading or a mix of the two. 21533 * 21534 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 21535 * asynchronously. If this option is given as "false", then the "onLoad" 21536 * callback must be given, as the instance returned from this constructor will 21537 * not be usable for a while. 21538 * 21539 * <li><i>loadParams</i> - an object containing parameters to pass to the 21540 * loader callback function when locale data is missing. The parameters are not 21541 * interpretted or modified in any way. They are simply passed along. The object 21542 * may contain any property/value pairs as long as the calling code is in 21543 * agreement with the loader callback function as to what those parameters mean. 21544 * </ul> 21545 * 21546 * When an address cannot be parsed properly, the entire address will be placed 21547 * into the streetAddress property.<p> 21548 * 21549 * When the freeformAddress is another Address, this will act like a copy 21550 * constructor.<p> 21551 * 21552 * 21553 * @constructor 21554 * @param {string|Address} freeformAddress free-form address to parse, or a 21555 * javascript object containing the fields 21556 * @param {Object} options options to the parser 21557 */ 21558 var Address = function (freeformAddress, options) { 21559 var address; 21560 21561 if (!freeformAddress) { 21562 return undefined; 21563 } 21564 21565 this.sync = true; 21566 this.loadParams = {}; 21567 21568 if (options) { 21569 if (options.locale) { 21570 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21571 } 21572 21573 if (typeof(options.sync) !== 'undefined') { 21574 this.sync = (options.sync == true); 21575 } 21576 21577 if (options.loadParams) { 21578 this.loadParams = options.loadParams; 21579 } 21580 } 21581 21582 this.locale = this.locale || new Locale(); 21583 // initialize from an already parsed object 21584 if (typeof(freeformAddress) === 'object') { 21585 /** 21586 * The street address, including house numbers and all. 21587 * @type {string|undefined} 21588 */ 21589 this.streetAddress = freeformAddress.streetAddress; 21590 /** 21591 * The locality of this address (usually a city or town). 21592 * @type {string|undefined} 21593 */ 21594 this.locality = freeformAddress.locality; 21595 /** 21596 * The region (province, canton, prefecture, state, etc.) where the address is located. 21597 * @type {string|undefined} 21598 */ 21599 this.region = freeformAddress.region; 21600 /** 21601 * Country-specific code for expediting mail. In the US, this is the zip code. 21602 * @type {string|undefined} 21603 */ 21604 this.postalCode = freeformAddress.postalCode; 21605 /** 21606 * Optional city-specific code for a particular post office, used to expidite 21607 * delivery. 21608 * @type {string|undefined} 21609 */ 21610 this.postOffice = freeformAddress.postOffice; 21611 /** 21612 * The country of the address. 21613 * @type {string|undefined} 21614 */ 21615 this.country = freeformAddress.country; 21616 if (freeformAddress.countryCode) { 21617 /** 21618 * The 2 or 3 letter ISO 3166 region code for the destination country in this address. 21619 * @type {string} 21620 */ 21621 this.countryCode = freeformAddress.countryCode; 21622 } 21623 if (freeformAddress.format) { 21624 /** 21625 * private 21626 * @type {string} 21627 */ 21628 this.format = freeformAddress.format; 21629 } 21630 return this; 21631 } 21632 21633 address = freeformAddress.replace(/[ \t\r]+/g, " ").trim(); 21634 address = address.replace(/[\s\n]+$/, ""); 21635 address = address.replace(/^[\s\n]+/, ""); 21636 //console.log("\n\n-------------\nAddress is '" + address + "'"); 21637 21638 this.lines = address.split(/[,,\n]/g); 21639 this.removeEmptyLines(this.lines); 21640 21641 isAscii._init(this.sync, this.loadParams, ilib.bind(this, function() { 21642 isIdeo._init(this.sync, this.loadParams, ilib.bind(this, function() { 21643 isDigit._init(this.sync, this.loadParams, ilib.bind(this, function() { 21644 if (typeof(ilib.data.nativecountries) === 'undefined') { 21645 Utils.loadData({ 21646 object: Address, 21647 name: "nativecountries.json", // countries in their own language 21648 locale: "-", // only need to load the root file 21649 nonlocale: true, 21650 sync: this.sync, 21651 loadParams: this.loadParams, 21652 callback: ilib.bind(this, function(nativecountries) { 21653 ilib.data.nativecountries = nativecountries; 21654 this._loadCountries(options && options.onLoad); 21655 }) 21656 }); 21657 } else { 21658 this._loadCountries(options && options.onLoad); 21659 } 21660 })); 21661 })); 21662 })); 21663 }; 21664 21665 /** @protected */ 21666 Address.prototype = { 21667 /** 21668 * @private 21669 */ 21670 _loadCountries: function(onLoad) { 21671 if (typeof(ilib.data.countries) === 'undefined') { 21672 Utils.loadData({ 21673 object: Address, 21674 name: "countries.json", // countries in English 21675 locale: "-", // only need to load the root file 21676 nonlocale: true, 21677 sync: this.sync, 21678 loadParams: this.loadParams, 21679 callback: ilib.bind(this, function(countries) { 21680 ilib.data.countries = countries; 21681 this._loadCtrynames(onLoad); 21682 }) 21683 }); 21684 } else { 21685 this._loadCtrynames(onLoad); 21686 } 21687 }, 21688 21689 /** 21690 * @private 21691 */ 21692 _loadCtrynames: function(onLoad) { 21693 Utils.loadData({ 21694 name: "ctrynames.json", 21695 object: Address, 21696 locale: this.locale, 21697 sync: this.sync, 21698 loadParams: this.loadParams, 21699 callback: ilib.bind(this, function(ctrynames) { 21700 this._determineDest(ctrynames, onLoad); 21701 }) 21702 }); 21703 }, 21704 21705 /** 21706 * @private 21707 * @param {Object?} ctrynames 21708 */ 21709 _findDest: function (ctrynames) { 21710 var match; 21711 21712 for (var countryName in ctrynames) { 21713 if (countryName && countryName !== "generated") { 21714 // find the longest match in the current table 21715 // ctrynames contains the country names mapped to region code 21716 // for efficiency, only test for things longer than the current match 21717 if (!match || match.text.length < countryName.length) { 21718 var temp = this._findCountry(countryName); 21719 if (temp) { 21720 match = temp; 21721 this.country = match.text; 21722 this.countryCode = ctrynames[countryName]; 21723 } 21724 } 21725 } 21726 } 21727 return match; 21728 }, 21729 21730 /** 21731 * @private 21732 * @param {Object?} localizedCountries 21733 * @param {function(Address):undefined} callback 21734 */ 21735 _determineDest: function (localizedCountries, callback) { 21736 var match; 21737 21738 /* 21739 * First, find the name of the destination country, as that determines how to parse 21740 * the rest of the address. For any address, there are three possible ways 21741 * that the name of the country could be written: 21742 * 1. In the current language 21743 * 2. In its own native language 21744 * 3. In English 21745 * We'll try all three. 21746 */ 21747 var tables = []; 21748 if (localizedCountries) { 21749 tables.push(localizedCountries); 21750 } 21751 tables.push(ilib.data.nativecountries); 21752 tables.push(ilib.data.countries); 21753 21754 for (var i = 0; i < tables.length; i++) { 21755 match = this._findDest(tables[i]); 21756 21757 if (match) { 21758 this.lines[match.line] = this.lines[match.line].substring(0, match.start) + this.lines[match.line].substring(match.start + match.text.length); 21759 21760 this._init(callback); 21761 return; 21762 } 21763 } 21764 21765 // no country, so try parsing it as if we were in the same country 21766 this.country = undefined; 21767 this.countryCode = this.locale.getRegion(); 21768 this._init(callback); 21769 }, 21770 21771 /** 21772 * @private 21773 * @param {function(Address):undefined} callback 21774 */ 21775 _init: function(callback) { 21776 Utils.loadData({ 21777 object: Address, 21778 locale: new Locale(this.countryCode), 21779 name: "address.json", 21780 sync: this.sync, 21781 loadParams: this.loadParams, 21782 callback: ilib.bind(this, function(info) { 21783 if (!info || JSUtils.isEmpty(info)) { 21784 // load the "unknown" locale instead 21785 Utils.loadData({ 21786 object: Address, 21787 locale: new Locale("XX"), 21788 name: "address.json", 21789 sync: this.sync, 21790 loadParams: this.loadParams, 21791 callback: ilib.bind(this, function(info) { 21792 this.info = info; 21793 this._parseAddress(); 21794 if (typeof(callback) === 'function') { 21795 callback(this); 21796 } 21797 }) 21798 }); 21799 } else { 21800 this.info = info; 21801 this._parseAddress(); 21802 if (typeof(callback) === 'function') { 21803 callback(this); 21804 } 21805 } 21806 }) 21807 }); 21808 }, 21809 21810 /** 21811 * @private 21812 */ 21813 _parseAddress: function() { 21814 // clean it up first 21815 var i, 21816 asianChars = 0, 21817 latinChars = 0, 21818 startAt, 21819 infoFields, 21820 field, 21821 pattern, 21822 matchFunction, 21823 match, 21824 fieldNumber; 21825 21826 // for locales that support both latin and asian character addresses, 21827 // decide if we are parsing an asian or latin script address 21828 if (this.info && this.info.multiformat) { 21829 for (var j = 0; j < this.lines.length; j++) { 21830 var line = new IString(this.lines[j]); 21831 var it = line.charIterator(); 21832 while (it.hasNext()) { 21833 var c = it.next(); 21834 if (isIdeo(c) || CType.withinRange(c, "Hangul")) { 21835 asianChars++; 21836 } else if (isAscii(c) && !isDigit(c)) { 21837 latinChars++; 21838 } 21839 } 21840 } 21841 21842 this.format = (asianChars >= latinChars) ? "asian" : "latin"; 21843 startAt = this.info.startAt[this.format]; 21844 infoFields = this.info.fields[this.format]; 21845 // //console.log("multiformat locale: format is now " + this.format); 21846 } else { 21847 startAt = (this.info && this.info.startAt) || "end"; 21848 infoFields = this.info.fields; 21849 } 21850 this.compare = (startAt === "end") ? this.endsWith : this.startsWith; 21851 21852 //console.log("this.lines is: " + JSON.stringify(this.lines)); 21853 21854 for (i = 0; i < infoFields.length && this.lines.length > 0; i++) { 21855 field = infoFields[i]; 21856 this.removeEmptyLines(this.lines); 21857 //console.log("Searching for field " + field.name); 21858 if (field.pattern) { 21859 if (typeof(field.pattern) === 'string') { 21860 pattern = new RegExp(field.pattern, "img"); 21861 matchFunction = this.matchRegExp; 21862 } else { 21863 pattern = field.pattern; 21864 matchFunction = this.matchPattern; 21865 } 21866 21867 switch (field.line) { 21868 case 'startAtFirst': 21869 for (fieldNumber = 0; fieldNumber < this.lines.length; fieldNumber++) { 21870 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21871 if (match) { 21872 break; 21873 } 21874 } 21875 break; 21876 case 'startAtLast': 21877 for (fieldNumber = this.lines.length-1; fieldNumber >= 0; fieldNumber--) { 21878 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21879 if (match) { 21880 break; 21881 } 21882 } 21883 break; 21884 case 'first': 21885 fieldNumber = 0; 21886 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21887 break; 21888 case 'last': 21889 default: 21890 fieldNumber = this.lines.length - 1; 21891 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21892 break; 21893 } 21894 if (match) { 21895 // //console.log("found match for " + field.name + ": " + JSON.stringify(match)); 21896 // //console.log("remaining line is " + match.line); 21897 this.lines[fieldNumber] = match.line; 21898 this[field.name] = match.match; 21899 } 21900 } else { 21901 // if nothing is given, default to taking the whole field 21902 this[field.name] = this.lines.splice(fieldNumber,1)[0].trim(); 21903 //console.log("typeof(this[field.name]) is " + typeof(this[field.name]) + " and value is " + JSON.stringify(this[field.name])); 21904 } 21905 } 21906 21907 // all the left overs go in the street address field 21908 this.removeEmptyLines(this.lines); 21909 if (this.lines.length > 0) { 21910 //console.log("this.lines is " + JSON.stringify(this.lines) + " and splicing to get streetAddress"); 21911 // Korea uses spaces between words, despite being an "asian" locale 21912 var joinString = (this.info.joinString && this.info.joinString[this.format]) || ((this.format && this.format === "asian") ? "" : ", "); 21913 this.streetAddress = this.lines.join(joinString).trim(); 21914 } 21915 21916 this.lines = undefined; 21917 //console.log("final result is " + JSON.stringify(this)); 21918 }, 21919 21920 /** 21921 * @protected 21922 * Find the named country either at the end or the beginning of the address. 21923 */ 21924 _findCountry: function(name) { 21925 var start = -1, match, line = 0; 21926 21927 if (this.lines.length > 0) { 21928 start = this.startsWith(this.lines[line], name); 21929 if (start === -1) { 21930 line = this.lines.length-1; 21931 start = this.endsWith(this.lines[line], name); 21932 } 21933 if (start !== -1) { 21934 match = { 21935 text: this.lines[line].substring(start, start + name.length), 21936 line: line, 21937 start: start 21938 }; 21939 } 21940 } 21941 21942 return match; 21943 }, 21944 21945 endsWith: function (subject, query) { 21946 var start = subject.length-query.length, 21947 i, 21948 pat; 21949 //console.log("endsWith: checking " + query + " against " + subject); 21950 for (i = 0; i < query.length; i++) { 21951 // TODO: use case mapper instead of toLowerCase() 21952 if (subject.charAt(start+i).toLowerCase() !== query.charAt(i).toLowerCase()) { 21953 return -1; 21954 } 21955 } 21956 if (start > 0) { 21957 pat = /\s/; 21958 if (!pat.test(subject.charAt(start-1))) { 21959 // make sure if we are not at the beginning of the string, that the match is 21960 // not the end of some other word 21961 return -1; 21962 } 21963 } 21964 return start; 21965 }, 21966 21967 startsWith: function (subject, query) { 21968 var i; 21969 // //console.log("startsWith: checking " + query + " against " + subject); 21970 for (i = 0; i < query.length; i++) { 21971 // TODO: use case mapper instead of toLowerCase() 21972 if (subject.charAt(i).toLowerCase() !== query.charAt(i).toLowerCase()) { 21973 return -1; 21974 } 21975 } 21976 return 0; 21977 }, 21978 21979 removeEmptyLines: function (arr) { 21980 var i = 0; 21981 21982 while (i < arr.length) { 21983 if (arr[i]) { 21984 arr[i] = arr[i].trim(); 21985 if (arr[i].length === 0) { 21986 arr.splice(i,1); 21987 } else { 21988 i++; 21989 } 21990 } else { 21991 arr.splice(i,1); 21992 } 21993 } 21994 }, 21995 21996 matchRegExp: function(address, line, expression, matchGroup, startAt) { 21997 var lastMatch, 21998 match, 21999 ret = {}, 22000 last; 22001 22002 //console.log("searching for regexp " + expression.source + " in line " + line); 22003 22004 match = expression.exec(line); 22005 if (startAt === 'end') { 22006 while (match !== null && match.length > 0) { 22007 //console.log("found matches " + JSON.stringify(match)); 22008 lastMatch = match; 22009 match = expression.exec(line); 22010 } 22011 match = lastMatch; 22012 } 22013 22014 if (match && match !== null) { 22015 //console.log("found matches " + JSON.stringify(match)); 22016 matchGroup = matchGroup || 0; 22017 if (match[matchGroup] !== undefined) { 22018 ret.match = match[matchGroup].trim(); 22019 ret.match = ret.match.replace(/^\-|\-+$/, ''); 22020 ret.match = ret.match.replace(/\s+$/, ''); 22021 last = (startAt === 'end') ? line.lastIndexOf(match[matchGroup]) : line.indexOf(match[matchGroup]); 22022 //console.log("last is " + last); 22023 ret.line = line.slice(0,last); 22024 if (address.format !== "asian") { 22025 ret.line += " "; 22026 } 22027 ret.line += line.slice(last+match[matchGroup].length); 22028 ret.line = ret.line.trim(); 22029 //console.log("found match " + ret.match + " from matchgroup " + matchGroup + " and rest of line is " + ret.line); 22030 return ret; 22031 } 22032 //} else { 22033 //console.log("no match"); 22034 } 22035 22036 return undefined; 22037 }, 22038 22039 matchPattern: function(address, line, pattern, matchGroup) { 22040 var start, 22041 j, 22042 ret = {}; 22043 22044 //console.log("searching in line " + line); 22045 22046 // search an array of possible fixed strings 22047 //console.log("Using fixed set of strings."); 22048 for (j = 0; j < pattern.length; j++) { 22049 start = address.compare(line, pattern[j]); 22050 if (start !== -1) { 22051 ret.match = line.substring(start, start+pattern[j].length); 22052 if (start !== 0) { 22053 ret.line = line.substring(0,start).trim(); 22054 } else { 22055 ret.line = line.substring(pattern[j].length).trim(); 22056 } 22057 //console.log("found match " + ret.match + " and rest of line is " + ret.line); 22058 return ret; 22059 } 22060 } 22061 22062 return undefined; 22063 } 22064 }; 22065 22066 22067 22068 /*< AddressFmt.js */ 22069 /* 22070 * AddressFmt.js - Format an address 22071 * 22072 * Copyright © 2013-2015, JEDLSoft 22073 * 22074 * Licensed under the Apache License, Version 2.0 (the "License"); 22075 * you may not use this file except in compliance with the License. 22076 * You may obtain a copy of the License at 22077 * 22078 * http://www.apache.org/licenses/LICENSE-2.0 22079 * 22080 * Unless required by applicable law or agreed to in writing, software 22081 * distributed under the License is distributed on an "AS IS" BASIS, 22082 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22083 * 22084 * See the License for the specific language governing permissions and 22085 * limitations under the License. 22086 */ 22087 22088 /* !depends 22089 ilib.js 22090 Locale.js 22091 Address.js 22092 IString.js 22093 Utils.js 22094 JSUtils.js 22095 */ 22096 22097 // !data address 22098 22099 22100 22101 /** 22102 * @class 22103 * Create a new formatter object to format physical addresses in a particular way. 22104 * 22105 * The options object may contain the following properties, both of which are optional: 22106 * 22107 * <ul> 22108 * <li><i>locale</i> - the locale to use to format this address. If not specified, it uses the default locale 22109 * 22110 * <li><i>style</i> - the style of this address. The default style for each country usually includes all valid 22111 * fields for that country. 22112 * 22113 * <li><i>onLoad</i> - a callback function to call when the address info for the 22114 * locale is fully loaded and the address has been parsed. When the onLoad 22115 * option is given, the address formatter object 22116 * will attempt to load any missing locale data using the ilib loader callback. 22117 * When the constructor is done (even if the data is already preassembled), the 22118 * onLoad function is called with the current instance as a parameter, so this 22119 * callback can be used with preassembled or dynamic loading or a mix of the two. 22120 * 22121 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 22122 * asynchronously. If this option is given as "false", then the "onLoad" 22123 * callback must be given, as the instance returned from this constructor will 22124 * not be usable for a while. 22125 * 22126 * <li><i>loadParams</i> - an object containing parameters to pass to the 22127 * loader callback function when locale data is missing. The parameters are not 22128 * interpretted or modified in any way. They are simply passed along. The object 22129 * may contain any property/value pairs as long as the calling code is in 22130 * agreement with the loader callback function as to what those parameters mean. 22131 * </ul> 22132 * 22133 * 22134 * @constructor 22135 * @param {Object} options options that configure how this formatter should work 22136 * Returns a formatter instance that can format multiple addresses. 22137 */ 22138 var AddressFmt = function(options) { 22139 this.sync = true; 22140 this.styleName = 'default'; 22141 this.loadParams = {}; 22142 this.locale = new Locale(); 22143 22144 if (options) { 22145 if (options.locale) { 22146 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 22147 } 22148 22149 if (typeof(options.sync) !== 'undefined') { 22150 this.sync = (options.sync == true); 22151 } 22152 22153 if (options.style) { 22154 this.styleName = options.style; 22155 } 22156 22157 if (options.loadParams) { 22158 this.loadParams = options.loadParams; 22159 } 22160 } 22161 22162 // console.log("Creating formatter for region: " + this.locale.region); 22163 Utils.loadData({ 22164 name: "address.json", 22165 object: AddressFmt, 22166 locale: this.locale, 22167 sync: this.sync, 22168 loadParams: this.loadParams, 22169 callback: ilib.bind(this, function(info) { 22170 if (!info || JSUtils.isEmpty(info)) { 22171 // load the "unknown" locale instead 22172 Utils.loadData({ 22173 name: "address.json", 22174 object: AddressFmt, 22175 locale: new Locale("XX"), 22176 sync: this.sync, 22177 loadParams: this.loadParams, 22178 callback: ilib.bind(this, function(info) { 22179 this.info = info; 22180 this._init(); 22181 if (options && typeof(options.onLoad) === 'function') { 22182 options.onLoad(this); 22183 } 22184 }) 22185 }); 22186 } else { 22187 this.info = info; 22188 this._init(); 22189 if (options && typeof(options.onLoad) === 'function') { 22190 options.onLoad(this); 22191 } 22192 } 22193 }) 22194 }); 22195 }; 22196 22197 /** 22198 * @private 22199 */ 22200 AddressFmt.prototype._init = function () { 22201 this.style = this.info && this.info.formats && this.info.formats[this.styleName]; 22202 22203 // use generic default -- should not happen, but just in case... 22204 this.style = this.style || (this.info && this.info.formats["default"]) || "{streetAddress}\n{locality} {region} {postalCode}\n{country}"; 22205 }; 22206 22207 /** 22208 * This function formats a physical address (Address instance) for display. 22209 * Whitespace is trimmed from the beginning and end of final resulting string, and 22210 * multiple consecutive whitespace characters in the middle of the string are 22211 * compressed down to 1 space character. 22212 * 22213 * If the Address instance is for a locale that is different than the locale for this 22214 * formatter, then a hybrid address is produced. The country name is located in the 22215 * correct spot for the current formatter's locale, but the rest of the fields are 22216 * formatted according to the default style of the locale of the actual address. 22217 * 22218 * Example: a mailing address in China, but formatted for the US might produce the words 22219 * "People's Republic of China" in English at the last line of the address, and the 22220 * Chinese-style address will appear in the first line of the address. In the US, the 22221 * country is on the last line, but in China the country is usually on the first line. 22222 * 22223 * @param {Address} address Address to format 22224 * @returns {string} Returns a string containing the formatted address 22225 */ 22226 AddressFmt.prototype.format = function (address) { 22227 var ret, template, other, format; 22228 22229 if (!address) { 22230 return ""; 22231 } 22232 // console.log("formatting address: " + JSON.stringify(address)); 22233 if (address.countryCode && 22234 address.countryCode !== this.locale.region && 22235 Locale._isRegionCode(this.locale.region) && 22236 this.locale.region !== "XX") { 22237 // we are formatting an address that is sent from this country to another country, 22238 // so only the country should be in this locale, and the rest should be in the other 22239 // locale 22240 // console.log("formatting for another locale. Loading in its settings: " + address.countryCode); 22241 other = new AddressFmt({ 22242 locale: new Locale(address.countryCode), 22243 style: this.styleName 22244 }); 22245 return other.format(address); 22246 } 22247 22248 if (typeof(this.style) === 'object') { 22249 format = this.style[address.format || "latin"]; 22250 } else { 22251 format = this.style; 22252 } 22253 22254 // console.log("Using format: " + format); 22255 // make sure we have a blank string for any missing parts so that 22256 // those template parts get blanked out 22257 var params = { 22258 country: address.country || "", 22259 region: address.region || "", 22260 locality: address.locality || "", 22261 streetAddress: address.streetAddress || "", 22262 postalCode: address.postalCode || "", 22263 postOffice: address.postOffice || "" 22264 }; 22265 template = new IString(format); 22266 ret = template.format(params); 22267 ret = ret.replace(/[ \t]+/g, ' '); 22268 ret = ret.replace("\n ", "\n"); 22269 ret = ret.replace(" \n", "\n"); 22270 return ret.replace(/\n+/g, '\n').trim(); 22271 }; 22272 22273 22274 22275 /*< GlyphString.js */ 22276 /* 22277 * GlyphString.js - ilib string subclass that allows you to access 22278 * whole glyphs at a time 22279 * 22280 * Copyright © 2015, JEDLSoft 22281 * 22282 * Licensed under the Apache License, Version 2.0 (the "License"); 22283 * you may not use this file except in compliance with the License. 22284 * You may obtain a copy of the License at 22285 * 22286 * http://www.apache.org/licenses/LICENSE-2.0 22287 * 22288 * Unless required by applicable law or agreed to in writing, software 22289 * distributed under the License is distributed on an "AS IS" BASIS, 22290 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22291 * 22292 * See the License for the specific language governing permissions and 22293 * limitations under the License. 22294 */ 22295 22296 // !depends IString.js CType.js Utils.js JSUtils.js 22297 // !data norm ctype_m 22298 22299 22300 22301 /** 22302 * @class 22303 * Create a new glyph string instance. This string inherits from 22304 * the IString class, and adds methods that allow you to access 22305 * whole glyphs at a time. <p> 22306 * 22307 * In Unicode, various accented characters can be created by using 22308 * a base character and one or more combining characters following 22309 * it. These appear on the screen to the user as a single glyph. 22310 * For example, the Latin character "a" (U+0061) followed by the 22311 * combining diaresis character "¨" (U+0308) combine together to 22312 * form the "a with diaresis" glyph "ä", which looks like a single 22313 * character on the screen.<p> 22314 * 22315 * The big problem with combining characters for web developers is 22316 * that many CSS engines do not ellipsize text between glyphs. They 22317 * only deal with single Unicode characters. So if a particular space 22318 * only allows for 4 characters, the CSS engine will truncate a 22319 * string at 4 Unicode characters and then add the ellipsis (...) 22320 * character. What if the fourth Unicode character is the "a" and 22321 * the fifth one is the diaresis? Then a string like "xxxäxxx" that 22322 * is ellipsized at 4 characters will appear as "xxxa..." on the 22323 * screen instead of "xxxä...".<p> 22324 * 22325 * In the Latin script as it is commonly used, it is not so common 22326 * to form accented characters using combining accents, so the above 22327 * example is mostly for illustrative purposes. It is not unheard of 22328 * however. The situation is much, much worse in scripts such as Thai and 22329 * Devanagari that normally make very heavy use of combining characters. 22330 * These scripts do so because Unicode does not include pre-composed 22331 * versions of the accented characters like it does for Latin, so 22332 * combining accents are the only way to create these accented and 22333 * combined versions of the characters.<p> 22334 * 22335 * The solution to thise problem is not to use the the CSS property 22336 * "text-overflow: ellipsis" in your web site, ever. Instead, use 22337 * a glyph string to truncate text between glyphs instead of between 22338 * characters.<p> 22339 * 22340 * Glyph strings are also useful for truncation, hyphenation, and 22341 * line wrapping, as all of these should be done between glyphs instead 22342 * of between characters.<p> 22343 * 22344 * The options parameter is optional, and may contain any combination 22345 * of the following properties:<p> 22346 * 22347 * <ul> 22348 * <li><i>onLoad</i> - a callback function to call when the locale data are 22349 * fully loaded. When the onLoad option is given, this object will attempt to 22350 * load any missing locale data using the ilib loader callback. 22351 * When the constructor is done (even if the data is already preassembled), the 22352 * onLoad function is called with the current instance as a parameter, so this 22353 * callback can be used with preassembled or dynamic loading or a mix of the two. 22354 * 22355 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 22356 * asynchronously. If this option is given as "false", then the "onLoad" 22357 * callback must be given, as the instance returned from this constructor will 22358 * not be usable for a while. 22359 * 22360 * <li><i>loadParams</i> - an object containing parameters to pass to the 22361 * loader callback function when locale data is missing. The parameters are not 22362 * interpretted or modified in any way. They are simply passed along. The object 22363 * may contain any property/value pairs as long as the calling code is in 22364 * agreement with the loader callback function as to what those parameters mean. 22365 * </ul> 22366 * 22367 * @constructor 22368 * @extends IString 22369 * @param {string|IString=} str initialize this instance with this string 22370 * @param {Object=} options options governing the way this instance works 22371 */ 22372 var GlyphString = function (str, options) { 22373 if (options && options.noinstance) { 22374 return; 22375 } 22376 22377 IString.call(this, str); 22378 22379 var sync = true; 22380 var loadParams = {}; 22381 if (options) { 22382 if (typeof(options.sync) === 'boolean') { 22383 sync = options.sync; 22384 } 22385 if (options.loadParams) { 22386 loadParams = options.loadParams; 22387 } 22388 } 22389 22390 CType._load("ctype_m", sync, loadParams, function() { 22391 if (!ilib.data.norm || JSUtils.isEmpty(ilib.data.norm.ccc)) { 22392 Utils.loadData({ 22393 object: GlyphString, 22394 locale: "-", 22395 name: "normdata.json", 22396 nonlocale: true, 22397 sync: sync, 22398 loadParams: loadParams, 22399 callback: ilib.bind(this, function (norm) { 22400 ilib.extend(ilib.data.norm, norm); 22401 if (options && typeof(options.onLoad) === 'function') { 22402 options.onLoad(this); 22403 } 22404 }) 22405 }); 22406 } else { 22407 if (options && typeof(options.onLoad) === 'function') { 22408 options.onLoad(this); 22409 } 22410 } 22411 }); 22412 }; 22413 22414 GlyphString.prototype = new IString(undefined); 22415 GlyphString.prototype.parent = IString; 22416 GlyphString.prototype.constructor = GlyphString; 22417 22418 /** 22419 * Return true if the given character is a leading Jamo (Choseong) character. 22420 * 22421 * @private 22422 * @static 22423 * @param {number} n code point to check 22424 * @return {boolean} true if the character is a leading Jamo character, 22425 * false otherwise 22426 */ 22427 GlyphString._isJamoL = function (n) { 22428 return (n >= 0x1100 && n <= 0x1112); 22429 }; 22430 22431 /** 22432 * Return true if the given character is a vowel Jamo (Jungseong) character. 22433 * 22434 * @private 22435 * @static 22436 * @param {number} n code point to check 22437 * @return {boolean} true if the character is a vowel Jamo character, 22438 * false otherwise 22439 */ 22440 GlyphString._isJamoV = function (n) { 22441 return (n >= 0x1161 && n <= 0x1175); 22442 }; 22443 22444 /** 22445 * Return true if the given character is a trailing Jamo (Jongseong) character. 22446 * 22447 * @private 22448 * @static 22449 * @param {number} n code point to check 22450 * @return {boolean} true if the character is a trailing Jamo character, 22451 * false otherwise 22452 */ 22453 GlyphString._isJamoT = function (n) { 22454 return (n >= 0x11A8 && n <= 0x11C2); 22455 }; 22456 22457 /** 22458 * Return true if the given character is a precomposed Hangul character. 22459 * 22460 * @private 22461 * @static 22462 * @param {number} n code point to check 22463 * @return {boolean} true if the character is a precomposed Hangul character, 22464 * false otherwise 22465 */ 22466 GlyphString._isHangul = function (n) { 22467 return (n >= 0xAC00 && n <= 0xD7A3); 22468 }; 22469 22470 /** 22471 * Algorithmically compose an L and a V combining Jamo characters into 22472 * a precomposed Korean syllabic Hangul character. Both should already 22473 * be in the proper ranges for L and V characters. 22474 * 22475 * @private 22476 * @static 22477 * @param {number} lead the code point of the lead Jamo character to compose 22478 * @param {number} trail the code point of the trailing Jamo character to compose 22479 * @return {string} the composed Hangul character 22480 */ 22481 GlyphString._composeJamoLV = function (lead, trail) { 22482 var lindex = lead - 0x1100; 22483 var vindex = trail - 0x1161; 22484 return IString.fromCodePoint(0xAC00 + (lindex * 21 + vindex) * 28); 22485 }; 22486 22487 /** 22488 * Algorithmically compose a Hangul LV and a combining Jamo T character 22489 * into a precomposed Korean syllabic Hangul character. 22490 * 22491 * @private 22492 * @static 22493 * @param {number} lead the code point of the lead Hangul character to compose 22494 * @param {number} trail the code point of the trailing Jamo T character to compose 22495 * @return {string} the composed Hangul character 22496 */ 22497 GlyphString._composeJamoLVT = function (lead, trail) { 22498 return IString.fromCodePoint(lead + (trail - 0x11A7)); 22499 }; 22500 22501 /** 22502 * Compose one character out of a leading character and a 22503 * trailing character. If the characters are Korean Jamo, they 22504 * will be composed algorithmically. If they are any other 22505 * characters, they will be looked up in the nfc tables. 22506 * 22507 * @private 22508 * @static 22509 * @param {string} lead leading character to compose 22510 * @param {string} trail the trailing character to compose 22511 * @return {string|null} the fully composed character, or undefined if 22512 * there is no composition for those two characters 22513 */ 22514 GlyphString._compose = function (lead, trail) { 22515 var first = lead.charCodeAt(0); 22516 var last = trail.charCodeAt(0); 22517 if (GlyphString._isHangul(first) && GlyphString._isJamoT(last)) { 22518 return GlyphString._composeJamoLVT(first, last); 22519 } else if (GlyphString._isJamoL(first) && GlyphString._isJamoV(last)) { 22520 return GlyphString._composeJamoLV(first, last); 22521 } 22522 22523 var c = lead + trail; 22524 return (ilib.data.norm.nfc && ilib.data.norm.nfc[c]); 22525 }; 22526 22527 /** 22528 * Return an iterator that will step through all of the characters 22529 * in the string one at a time, taking care to step through decomposed 22530 * characters and through surrogate pairs in the UTF-16 encoding 22531 * as single characters. <p> 22532 * 22533 * The GlyphString class will return decomposed Unicode characters 22534 * as a single unit that a user might see on the screen as a single 22535 * glyph. If the 22536 * next character in the iteration is a base character and it is 22537 * followed by combining characters, the base and all its following 22538 * combining characters are returned as a single unit.<p> 22539 * 22540 * The standard Javascript String's charAt() method only 22541 * returns information about a particular 16-bit character in the 22542 * UTF-16 encoding scheme. 22543 * If the index is pointing to a low- or high-surrogate character, 22544 * it will return that surrogate character rather 22545 * than the surrogate pair which represents a character 22546 * in the supplementary planes.<p> 22547 * 22548 * The iterator instance returned has two methods, hasNext() which 22549 * returns true if the iterator has more characters to iterate through, 22550 * and next() which returns the next character.<p> 22551 * 22552 * @override 22553 * @return {Object} an iterator 22554 * that iterates through all the characters in the string 22555 */ 22556 GlyphString.prototype.charIterator = function() { 22557 var it = IString.prototype.charIterator.call(this); 22558 22559 /** 22560 * @constructor 22561 */ 22562 function _chiterator (istring) { 22563 this.index = 0; 22564 this.spacingCombining = false; 22565 this.hasNext = function () { 22566 return !!this.nextChar || it.hasNext(); 22567 }; 22568 this.next = function () { 22569 var ch = this.nextChar || it.next(), 22570 prevCcc = ilib.data.norm.ccc[ch], 22571 nextCcc, 22572 composed = ch; 22573 22574 this.nextChar = undefined; 22575 this.spacingCombining = false; 22576 22577 if (ilib.data.norm.ccc && 22578 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ilib.data.norm.ccc[ch] === 0)) { 22579 // found a starter... find all the non-starters until the next starter. Must include 22580 // the next starter because under some odd circumstances, two starters sometimes recompose 22581 // together to form another character 22582 var notdone = true; 22583 while (it.hasNext() && notdone) { 22584 this.nextChar = it.next(); 22585 nextCcc = ilib.data.norm.ccc[this.nextChar]; 22586 var codePoint = IString.toCodePoint(this.nextChar, 0); 22587 // Mn characters are Marks that are non-spacing. These do not take more room than an accent, so they should be 22588 // considered part of the on-screen glyph, even if they are non-combining. Mc are marks that are spacing 22589 // and combining, which means they are part of the glyph, but they cause the glyph to use up more space than 22590 // just the base character alone. 22591 var isMn = CType._inRange(codePoint, "Mn", ilib.data.ctype_m); 22592 var isMc = CType._inRange(codePoint, "Mc", ilib.data.ctype_m); 22593 if (isMn || isMc || (typeof(nextCcc) !== 'undefined' && nextCcc !== 0)) { 22594 if (isMc) { 22595 this.spacingCombining = true; 22596 } 22597 ch += this.nextChar; 22598 this.nextChar = undefined; 22599 } else { 22600 // found the next starter. See if this can be composed with the previous starter 22601 var testChar = GlyphString._compose(composed, this.nextChar); 22602 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 22603 // not blocked and there is a mapping 22604 composed = testChar; 22605 ch += this.nextChar; 22606 this.nextChar = undefined; 22607 } else { 22608 // finished iterating, leave this.nextChar for the next next() call 22609 notdone = false; 22610 } 22611 } 22612 prevCcc = nextCcc; 22613 } 22614 } 22615 return ch; 22616 }; 22617 // Returns true if the last character returned by the "next" method included 22618 // spacing combining characters. If it does, then the character was wider than 22619 // just the base character alone, and the truncation code will not add it. 22620 this.wasSpacingCombining = function() { 22621 return this.spacingCombining; 22622 }; 22623 }; 22624 return new _chiterator(this); 22625 }; 22626 22627 /** 22628 * Truncate the current string at the given number of whole glyphs and return 22629 * the resulting string. 22630 * 22631 * @param {number} length the number of whole glyphs to keep in the string 22632 * @return {string} a string truncated to the requested number of glyphs 22633 */ 22634 GlyphString.prototype.truncate = function(length) { 22635 var it = this.charIterator(); 22636 var tr = ""; 22637 for (var i = 0; i < length-1 && it.hasNext(); i++) { 22638 tr += it.next(); 22639 } 22640 22641 /* 22642 * handle the last character separately. If it contains spacing combining 22643 * accents, then we must assume that it uses up more horizontal space on 22644 * the screen than just the base character by itself, and therefore this 22645 * method will not truncate enough characters to fit in the given length. 22646 * In this case, we have to chop off not only the combining characters, 22647 * but also the base character as well because the base without the 22648 * combining accents is considered a different character. 22649 */ 22650 if (i < length && it.hasNext()) { 22651 var c = it.next(); 22652 if (!it.wasSpacingCombining()) { 22653 tr += c; 22654 } 22655 } 22656 return tr; 22657 }; 22658 22659 /** 22660 * Truncate the current string at the given number of glyphs and add an ellipsis 22661 * to indicate that is more to the string. The ellipsis forms the last character 22662 * in the string, so the string is actually truncated at length-1 glyphs. 22663 * 22664 * @param {number} length the number of whole glyphs to keep in the string 22665 * including the ellipsis 22666 * @return {string} a string truncated to the requested number of glyphs 22667 * with an ellipsis 22668 */ 22669 GlyphString.prototype.ellipsize = function(length) { 22670 return this.truncate(length > 0 ? length-1 : 0) + "…"; 22671 }; 22672 22673 22674 22675 /*< NormString.js */ 22676 /* 22677 * NormString.js - ilib normalized string subclass definition 22678 * 22679 * Copyright © 2013-2015, JEDLSoft 22680 * 22681 * Licensed under the Apache License, Version 2.0 (the "License"); 22682 * you may not use this file except in compliance with the License. 22683 * You may obtain a copy of the License at 22684 * 22685 * http://www.apache.org/licenses/LICENSE-2.0 22686 * 22687 * Unless required by applicable law or agreed to in writing, software 22688 * distributed under the License is distributed on an "AS IS" BASIS, 22689 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22690 * 22691 * See the License for the specific language governing permissions and 22692 * limitations under the License. 22693 */ 22694 22695 // !depends IString.js GlyphString.js Utils.js 22696 22697 22698 22699 /** 22700 * @class 22701 * Create a new normalized string instance. This string inherits from 22702 * the GlyphString class, and adds the normalize method. It can be 22703 * used anywhere that a normal Javascript string is used. <p> 22704 * 22705 * 22706 * @constructor 22707 * @extends GlyphString 22708 * @param {string|IString=} str initialize this instance with this string 22709 */ 22710 var NormString = function (str) { 22711 GlyphString.call(this, str); 22712 }; 22713 22714 NormString.prototype = new GlyphString("", {noinstance:true}); 22715 NormString.prototype.parent = GlyphString; 22716 NormString.prototype.constructor = NormString; 22717 22718 /** 22719 * Initialize the normalized string routines statically. This 22720 * is intended to be called in a dynamic-load version of ilib 22721 * to load the data need to normalize strings before any instances 22722 * of NormString are created.<p> 22723 * 22724 * The options parameter may contain any of the following properties: 22725 * 22726 * <ul> 22727 * <li><i>form</i> - {string} the normalization form to load 22728 * <li><i>script</i> - {string} load the normalization for this script. If the 22729 * script is given as "all" then the normalization data for all scripts 22730 * is loaded at the same time 22731 * <li><i>sync</i> - {boolean} whether to load the files synchronously or not 22732 * <li><i>loadParams</i> - {Object} parameters to the loader function 22733 * <li><i>onLoad</i> - {function()} a function to call when the 22734 * files are done being loaded 22735 * </ul> 22736 * 22737 * @param {Object} options an object containing properties that govern 22738 * how to initialize the data 22739 */ 22740 NormString.init = function(options) { 22741 if (!ilib._load || (typeof(ilib._load) !== 'function' && typeof(ilib._load.loadFiles) !== 'function')) { 22742 // can't do anything 22743 return; 22744 } 22745 var form = "nfkc"; 22746 var script = "all"; 22747 var sync = true; 22748 var onLoad = undefined; 22749 var loadParams = undefined; 22750 if (options) { 22751 form = options.form || "nfkc"; 22752 script = options.script || "all"; 22753 sync = typeof(options.sync) !== 'undefined' ? options.sync : true; 22754 onLoad = typeof(options.onLoad) === 'function' ? options.onLoad : undefined; 22755 if (options.loadParams) { 22756 loadParams = options.loadParams; 22757 } 22758 } 22759 var formDependencies = { 22760 "nfd": ["nfd"], 22761 "nfc": ["nfd"], 22762 "nfkd": ["nfkd", "nfd"], 22763 "nfkc": ["nfkd", "nfd"] 22764 }; 22765 var files = ["normdata.json"]; 22766 var forms = formDependencies[form]; 22767 for (var f in forms) { 22768 files.push(forms[f] + "/" + script + ".json"); 22769 } 22770 22771 if (JSUtils.isEmpty(ilib.data.norm.ccc) || JSUtils.isEmpty(ilib.data.norm.nfd) || JSUtils.isEmpty(ilib.data.norm.nfkd)) { 22772 //console.log("loading files " + JSON.stringify(files)); 22773 Utils._callLoadData(files, sync, loadParams, function(arr) { 22774 ilib.extend(ilib.data.norm, arr[0]); 22775 for (var i = 1; i < arr.length; i++) { 22776 if (typeof(arr[i]) !== 'undefined') { 22777 ilib.extend(ilib.data.norm[forms[i-1]], arr[i]); 22778 } 22779 } 22780 22781 if (onLoad) { 22782 onLoad(arr); 22783 } 22784 }); 22785 } 22786 }; 22787 22788 /** 22789 * Algorithmically decompose a precomposed Korean syllabic Hangul 22790 * character into its individual combining Jamo characters. The given 22791 * character must be in the range of Hangul characters U+AC00 to U+D7A3. 22792 * 22793 * @private 22794 * @static 22795 * @param {number} cp code point of a Korean Hangul character to decompose 22796 * @return {string} the decomposed string of Jamo characters 22797 */ 22798 NormString._decomposeHangul = function (cp) { 22799 var sindex = cp - 0xAC00; 22800 var result = String.fromCharCode(0x1100 + sindex / 588) + 22801 String.fromCharCode(0x1161 + (sindex % 588) / 28); 22802 var t = sindex % 28; 22803 if (t !== 0) { 22804 result += String.fromCharCode(0x11A7 + t); 22805 } 22806 return result; 22807 }; 22808 22809 /** 22810 * Expand one character according to the given canonical and 22811 * compatibility mappings. 22812 * 22813 * @private 22814 * @static 22815 * @param {string} ch character to map 22816 * @param {Object} canon the canonical mappings to apply 22817 * @param {Object=} compat the compatibility mappings to apply, or undefined 22818 * if only the canonical mappings are needed 22819 * @return {string} the mapped character 22820 */ 22821 NormString._expand = function (ch, canon, compat) { 22822 var i, 22823 expansion = "", 22824 n = ch.charCodeAt(0); 22825 if (GlyphString._isHangul(n)) { 22826 expansion = NormString._decomposeHangul(n); 22827 } else { 22828 var result = canon[ch]; 22829 if (!result && compat) { 22830 result = compat[ch]; 22831 } 22832 if (result && result !== ch) { 22833 for (i = 0; i < result.length; i++) { 22834 expansion += NormString._expand(result[i], canon, compat); 22835 } 22836 } else { 22837 expansion = ch; 22838 } 22839 } 22840 return expansion; 22841 }; 22842 22843 /** 22844 * Perform the Unicode Normalization Algorithm upon the string and return 22845 * the resulting new string. The current string is not modified. 22846 * 22847 * <h2>Forms</h2> 22848 * 22849 * The forms of possible normalizations are defined by the <a 22850 * href="http://www.unicode.org/reports/tr15/">Unicode Standard 22851 * Annex (UAX) 15</a>. The form parameter is a string that may have one 22852 * of the following values: 22853 * 22854 * <ul> 22855 * <li>nfd - Canonical decomposition. This decomposes characters into 22856 * their exactly equivalent forms. For example, "ü" would decompose 22857 * into a "u" followed by the combining diaeresis character. 22858 * <li>nfc - Canonical decomposition followed by canonical composition. 22859 * This decomposes and then recomposes character into their shortest 22860 * exactly equivalent forms by recomposing as many combining characters 22861 * as possible. For example, "ü" followed by a combining 22862 * macron character would decompose into a "u" followed by the combining 22863 * macron characters the combining diaeresis character, and then be recomposed into 22864 * the u with macron and diaeresis "ṻ" character. The reason that 22865 * the "nfc" form decomposes and then recomposes is that combining characters 22866 * have a specific order under the Unicode Normalization Algorithm, and 22867 * partly composed characters such as the "ü" followed by combining 22868 * marks may change the order of the combining marks when decomposed and 22869 * recomposed. 22870 * <li>nfkd - Compatibility decomposition. This decomposes characters 22871 * into compatible forms that may not be exactly equivalent semantically, 22872 * as well as performing canonical decomposition as well. 22873 * For example, the "œ" ligature character decomposes to the two 22874 * characters "oe" because they are compatible even though they are not 22875 * exactly the same semantically. 22876 * <li>nfkc - Compatibility decomposition followed by canonical composition. 22877 * This decomposes characters into compatible forms, then recomposes 22878 * characters using the canonical composition. That is, it breaks down 22879 * characters into the compatible forms, and then recombines all combining 22880 * marks it can with their base characters. For example, the character 22881 * "ǽ" would be normalized to "aé" by first decomposing 22882 * the character into "a" followed by "e" followed by the combining acute accent 22883 * combining mark, and then recomposed to an "a" followed by the "e" 22884 * with acute accent. 22885 * </ul> 22886 * 22887 * <h2>Operation</h2> 22888 * 22889 * Two strings a and b can be said to be canonically equivalent if 22890 * normalize(a) = normalize(b) 22891 * under the nfc normalization form. Two strings can be said to be compatible if 22892 * normalize(a) = normalize(b) under the nfkc normalization form.<p> 22893 * 22894 * The canonical normalization is often used to see if strings are 22895 * equivalent to each other, and thus is useful when implementing parsing 22896 * algorithms or exact matching algorithms. It can also be used to ensure 22897 * that any string output produces a predictable sequence of characters.<p> 22898 * 22899 * Compatibility normalization 22900 * does not always preserve the semantic meaning of all the characters, 22901 * although this is sometimes the behaviour that you are after. It is useful, 22902 * for example, when doing searches of user-input against text in documents 22903 * where the matches are supposed to "fuzzy". In this case, both the query 22904 * string and the document string would be mapped to their compatibility 22905 * normalized forms, and then compared.<p> 22906 * 22907 * Compatibility normalization also does not guarantee round-trip conversion 22908 * to and from legacy character sets as the normalization is "lossy". It is 22909 * akin to doing a lower- or upper-case conversion on text -- after casing, 22910 * you cannot tell what case each character is in the original string. It is 22911 * good for matching and searching, but it rarely good for output because some 22912 * distinctions or meanings in the original text have been lost.<p> 22913 * 22914 * Note that W3C normalization for HTML also escapes and unescapes 22915 * HTML character entities such as "ü" for u with diaeresis. This 22916 * method does not do such escaping or unescaping. If normalization is required 22917 * for HTML strings with entities, unescaping should be performed on the string 22918 * prior to calling this method.<p> 22919 * 22920 * <h2>Data</h2> 22921 * 22922 * Normalization requires a fair amount of mapping data, much of which you may 22923 * not need for the characters expected in your texts. It is possible to assemble 22924 * a copy of ilib that saves space by only including normalization data for 22925 * those scripts that you expect to encounter in your data.<p> 22926 * 22927 * The normalization data is organized by normalization form and within there 22928 * by script. To include the normalization data for a particular script with 22929 * a particular normalization form, use the directive: 22930 * 22931 * <pre><code> 22932 * !depends <form>/<script>.js 22933 * </code></pre> 22934 * 22935 * Where <form> is the normalization form ("nfd", "nfc", "nfkd", or "nfkc"), and 22936 * <script> is the ISO 15924 code for the script you would like to 22937 * support. Example: to load in the NFC data for Cyrillic, you would use: 22938 * 22939 * <pre><code> 22940 * !depends nfc/Cyrl.js 22941 * </code></pre> 22942 * 22943 * Note that because certain normalization forms include others in their algorithm, 22944 * their data also depends on the data for the other forms. For example, if you 22945 * include the "nfc" data for a script, you will automatically get the "nfd" data 22946 * for that same script as well because the NFC algorithm does NFD normalization 22947 * first. Here are the dependencies:<p> 22948 * 22949 * <ul> 22950 * <li>NFD -> no dependencies 22951 * <li>NFC -> NFD 22952 * <li>NFKD -> NFD 22953 * <li>NFKC -> NFKD, NFD, NFC 22954 * </ul> 22955 * 22956 * A special value for the script dependency is "all" which will cause the data for 22957 * all scripts 22958 * to be loaded for that normalization form. This would be useful if you know that 22959 * you are going to normalize a lot of multilingual text or cannot predict which scripts 22960 * will appear in the input. Because the NFKC form depends on all others, you can 22961 * get all of the data for all forms automatically by depending on "nfkc/all.js". 22962 * Note that the normalization data for practically all script automatically depend 22963 * on data for the Common script (code "Zyyy") which contains all of the characters 22964 * that are commonly used in many different scripts. Examples of characters in the 22965 * Common script are the ASCII punctuation characters, or the ASCII Arabic 22966 * numerals "0" through "9".<p> 22967 * 22968 * By default, none of the data for normalization is automatically 22969 * included in the preassembled iliball.js file. 22970 * If you would like to normalize strings, you must assemble 22971 * your own copy of ilib and explicitly include the normalization data 22972 * for those scripts as per the instructions above. This normalization method will 22973 * produce output, even without the normalization data. However, the output will be 22974 * simply the same thing as its input for all scripts 22975 * except Korean Hangul and Jamo, which are decomposed and recomposed 22976 * algorithmically and therefore do not rely on data.<p> 22977 * 22978 * If characters are encountered for which there are no normalization data, they 22979 * will be passed through to the output string unmodified. 22980 * 22981 * @param {string} form The normalization form requested 22982 * @return {IString} a new instance of an IString that has been normalized 22983 * according to the requested form. The current instance is not modified. 22984 */ 22985 NormString.prototype.normalize = function (form) { 22986 var i; 22987 22988 if (typeof(form) !== 'string' || this.str.length === 0) { 22989 return new IString(this.str); 22990 } 22991 22992 var nfc = false, 22993 nfkd = false; 22994 22995 switch (form) { 22996 default: 22997 break; 22998 22999 case "nfc": 23000 nfc = true; 23001 break; 23002 23003 case "nfkd": 23004 nfkd = true; 23005 break; 23006 23007 case "nfkc": 23008 nfkd = true; 23009 nfc = true; 23010 break; 23011 } 23012 23013 // decompose 23014 var decomp = ""; 23015 23016 if (nfkd) { 23017 var ch, it = IString.prototype.charIterator.call(this); 23018 while (it.hasNext()) { 23019 ch = it.next(); 23020 decomp += NormString._expand(ch, ilib.data.norm.nfd, ilib.data.norm.nfkd); 23021 } 23022 } else { 23023 var ch, it = IString.prototype.charIterator.call(this); 23024 while (it.hasNext()) { 23025 ch = it.next(); 23026 decomp += NormString._expand(ch, ilib.data.norm.nfd); 23027 } 23028 } 23029 23030 // now put the combining marks in a fixed order by 23031 // sorting on the combining class 23032 function compareByCCC(left, right) { 23033 return ilib.data.norm.ccc[left] - ilib.data.norm.ccc[right]; 23034 } 23035 23036 function ccc(c) { 23037 return ilib.data.norm.ccc[c] || 0; 23038 } 23039 23040 function sortChars(arr, comp) { 23041 // qt/qml's Javascript engine re-arranges entries that are equal to 23042 // each other. Technically, that is a correct behaviour, but it is 23043 // not desirable. All the other engines leave equivalent entries 23044 // where they are. This bubblesort emulates what the other engines 23045 // do. Fortunately, the arrays we are sorting are a max of 5 or 6 23046 // entries, so performance is not a big deal here. 23047 if (ilib._getPlatform() === "qt") { 23048 var tmp; 23049 for (var i = arr.length-1; i > 0; i--) { 23050 for (var j = 0; j < i; j++) { 23051 if (comp(arr[j], arr[j+1]) > 0) { 23052 tmp = arr[j]; 23053 arr[j] = arr[j+1]; 23054 arr[j+1] = tmp; 23055 } 23056 } 23057 } 23058 return arr; 23059 } else { 23060 return arr.sort(comp); 23061 } 23062 } 23063 23064 var dstr = new IString(decomp); 23065 var it = dstr.charIterator(); 23066 var cpArray = []; 23067 23068 // easier to deal with as an array of chars 23069 while (it.hasNext()) { 23070 cpArray.push(it.next()); 23071 } 23072 23073 i = 0; 23074 while (i < cpArray.length) { 23075 if (typeof(ilib.data.norm.ccc[cpArray[i]]) !== 'undefined' && ccc(cpArray[i]) !== 0) { 23076 // found a non-starter... rearrange all the non-starters until the next starter 23077 var end = i+1; 23078 while (end < cpArray.length && 23079 typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 23080 ccc(cpArray[end]) !== 0) { 23081 end++; 23082 } 23083 23084 // simple sort of the non-starter chars 23085 if (end - i > 1) { 23086 cpArray = cpArray.slice(0,i).concat(sortChars(cpArray.slice(i, end), compareByCCC), cpArray.slice(end)); 23087 } 23088 } 23089 i++; 23090 } 23091 23092 if (nfc) { 23093 i = 0; 23094 while (i < cpArray.length) { 23095 if (typeof(ilib.data.norm.ccc[cpArray[i]]) === 'undefined' || ilib.data.norm.ccc[cpArray[i]] === 0) { 23096 // found a starter... find all the non-starters until the next starter. Must include 23097 // the next starter because under some odd circumstances, two starters sometimes recompose 23098 // together to form another character 23099 var end = i+1; 23100 var notdone = true; 23101 while (end < cpArray.length && notdone) { 23102 if (typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 23103 ilib.data.norm.ccc[cpArray[end]] !== 0) { 23104 if (ccc(cpArray[end-1]) < ccc(cpArray[end])) { 23105 // not blocked 23106 var testChar = GlyphString._compose(cpArray[i], cpArray[end]); 23107 if (typeof(testChar) !== 'undefined') { 23108 cpArray[i] = testChar; 23109 23110 // delete the combining char 23111 cpArray.splice(end,1); 23112 23113 // restart the iteration, just in case there is more to recompose with the new char 23114 end = i; 23115 } 23116 } 23117 end++; 23118 } else { 23119 // found the next starter. See if this can be composed with the previous starter 23120 var testChar = GlyphString._compose(cpArray[i], cpArray[end]); 23121 if (ccc(cpArray[end-1]) === 0 && typeof(testChar) !== 'undefined') { 23122 // not blocked and there is a mapping 23123 cpArray[i] = testChar; 23124 23125 // delete the combining char 23126 cpArray.splice(end,1); 23127 23128 // restart the iteration, just in case there is more to recompose with the new char 23129 end = i+1; 23130 } else { 23131 // finished iterating 23132 notdone = false; 23133 } 23134 } 23135 } 23136 } 23137 i++; 23138 } 23139 } 23140 23141 return new IString(cpArray.length > 0 ? cpArray.join("") : ""); 23142 }; 23143 23144 /** 23145 * @override 23146 * Return an iterator that will step through all of the characters 23147 * in the string one at a time, taking care to step through decomposed 23148 * characters and through surrogate pairs in UTF-16 encoding 23149 * properly. <p> 23150 * 23151 * The NormString class will return decomposed Unicode characters 23152 * as a single unit that a user might see on the screen. If the 23153 * next character in the iteration is a base character and it is 23154 * followed by combining characters, the base and all its following 23155 * combining characters are returned as a single unit.<p> 23156 * 23157 * The standard Javascript String's charAt() method only 23158 * returns information about a particular 16-bit character in the 23159 * UTF-16 encoding scheme. 23160 * If the index is pointing to a low- or high-surrogate character, 23161 * it will return that surrogate character rather 23162 * than the surrogate pair which represents a character 23163 * in the supplementary planes.<p> 23164 * 23165 * The iterator instance returned has two methods, hasNext() which 23166 * returns true if the iterator has more characters to iterate through, 23167 * and next() which returns the next character.<p> 23168 * 23169 * @return {Object} an iterator 23170 * that iterates through all the characters in the string 23171 */ 23172 NormString.prototype.charIterator = function() { 23173 var it = IString.prototype.charIterator.call(this); 23174 23175 /** 23176 * @constructor 23177 */ 23178 function _chiterator (istring) { 23179 /** 23180 * @private 23181 */ 23182 var ccc = function(c) { 23183 return ilib.data.norm.ccc[c] || 0; 23184 }; 23185 23186 this.index = 0; 23187 this.hasNext = function () { 23188 return !!this.nextChar || it.hasNext(); 23189 }; 23190 this.next = function () { 23191 var ch = this.nextChar || it.next(), 23192 prevCcc = ccc(ch), 23193 nextCcc, 23194 composed = ch; 23195 23196 this.nextChar = undefined; 23197 23198 if (ilib.data.norm.ccc && 23199 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ccc(ch) === 0)) { 23200 // found a starter... find all the non-starters until the next starter. Must include 23201 // the next starter because under some odd circumstances, two starters sometimes recompose 23202 // together to form another character 23203 var notdone = true; 23204 while (it.hasNext() && notdone) { 23205 this.nextChar = it.next(); 23206 nextCcc = ccc(this.nextChar); 23207 if (typeof(ilib.data.norm.ccc[this.nextChar]) !== 'undefined' && nextCcc !== 0) { 23208 ch += this.nextChar; 23209 this.nextChar = undefined; 23210 } else { 23211 // found the next starter. See if this can be composed with the previous starter 23212 var testChar = GlyphString._compose(composed, this.nextChar); 23213 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 23214 // not blocked and there is a mapping 23215 composed = testChar; 23216 ch += this.nextChar; 23217 this.nextChar = undefined; 23218 } else { 23219 // finished iterating, leave this.nextChar for the next next() call 23220 notdone = false; 23221 } 23222 } 23223 prevCcc = nextCcc; 23224 } 23225 } 23226 return ch; 23227 }; 23228 }; 23229 return new _chiterator(this); 23230 }; 23231 23232 23233 /*< CodePointSource.js */ 23234 /* 23235 * CodePointSource.js - Source of code points from a string 23236 * 23237 * Copyright © 2013-2015, JEDLSoft 23238 * 23239 * Licensed under the Apache License, Version 2.0 (the "License"); 23240 * you may not use this file except in compliance with the License. 23241 * You may obtain a copy of the License at 23242 * 23243 * http://www.apache.org/licenses/LICENSE-2.0 23244 * 23245 * Unless required by applicable law or agreed to in writing, software 23246 * distributed under the License is distributed on an "AS IS" BASIS, 23247 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23248 * 23249 * See the License for the specific language governing permissions and 23250 * limitations under the License. 23251 */ 23252 23253 // !depends isPunct.js NormString.js 23254 23255 23256 /** 23257 * @class 23258 * Represents a buffered source of code points. The input string is first 23259 * normalized so that combining characters come out in a standardized order. 23260 * If the "ignorePunctuation" flag is turned on, then punctuation 23261 * characters are skipped. 23262 * 23263 * @constructor 23264 * @private 23265 * @param {NormString|string} str a string to get code points from 23266 * @param {boolean} ignorePunctuation whether or not to ignore punctuation 23267 * characters 23268 */ 23269 var CodePointSource = function(str, ignorePunctuation) { 23270 this.chars = []; 23271 // first convert the string to a normalized sequence of characters 23272 var s = (typeof(str) === "string") ? new NormString(str) : str; 23273 this.it = s.charIterator(); 23274 this.ignorePunctuation = typeof(ignorePunctuation) === "boolean" && ignorePunctuation; 23275 }; 23276 23277 /** 23278 * Return the first num code points in the source without advancing the 23279 * source pointer. If there are not enough code points left in the 23280 * string to satisfy the request, this method will return undefined. 23281 * 23282 * @param {number} num the number of characters to peek ahead 23283 * @return {string|undefined} a string formed out of up to num code points from 23284 * the start of the string, or undefined if there are not enough character left 23285 * in the source to complete the request 23286 */ 23287 CodePointSource.prototype.peek = function(num) { 23288 if (num < 1) { 23289 return undefined; 23290 } 23291 if (this.chars.length < num && this.it.hasNext()) { 23292 for (var i = 0; this.chars.length < 4 && this.it.hasNext(); i++) { 23293 var c = this.it.next(); 23294 if (c && !this.ignorePunctuation || !isPunct(c)) { 23295 this.chars.push(c); 23296 } 23297 } 23298 } 23299 if (this.chars.length < num) { 23300 return undefined; 23301 } 23302 return this.chars.slice(0, num).join(""); 23303 }; 23304 /** 23305 * Advance the source pointer by the given number of code points. 23306 * @param {number} num number of code points to advance 23307 */ 23308 CodePointSource.prototype.consume = function(num) { 23309 if (num > 0) { 23310 this.peek(num); // for the iterator to go forward if needed 23311 if (num < this.chars.length) { 23312 this.chars = this.chars.slice(num); 23313 } else { 23314 this.chars = []; 23315 } 23316 } 23317 }; 23318 23319 23320 23321 /*< ElementIterator.js */ 23322 /* 23323 * ElementIterator.js - Iterate through a list of collation elements 23324 * 23325 * Copyright © 2013-2015, JEDLSoft 23326 * 23327 * Licensed under the Apache License, Version 2.0 (the "License"); 23328 * you may not use this file except in compliance with the License. 23329 * You may obtain a copy of the License at 23330 * 23331 * http://www.apache.org/licenses/LICENSE-2.0 23332 * 23333 * Unless required by applicable law or agreed to in writing, software 23334 * distributed under the License is distributed on an "AS IS" BASIS, 23335 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23336 * 23337 * See the License for the specific language governing permissions and 23338 * limitations under the License. 23339 */ 23340 23341 /** 23342 * @class 23343 * An iterator through a sequence of collation elements. This 23344 * iterator takes a source of code points, converts them into 23345 * collation elements, and allows the caller to get single 23346 * elements at a time. 23347 * 23348 * @constructor 23349 * @private 23350 * @param {CodePointSource} source source of code points to 23351 * convert to collation elements 23352 * @param {Object} map mapping from sequences of code points to 23353 * collation elements 23354 * @param {number} keysize size in bits of the collation elements 23355 */ 23356 var ElementIterator = function (source, map, keysize) { 23357 this.elements = []; 23358 this.source = source; 23359 this.map = map; 23360 this.keysize = keysize; 23361 }; 23362 23363 /** 23364 * @private 23365 */ 23366 ElementIterator.prototype._fillBuffer = function () { 23367 var str = undefined; 23368 23369 // peek ahead by up to 4 characters, which may combine 23370 // into 1 or more collation elements 23371 for (var i = 4; i > 0; i--) { 23372 str = this.source.peek(i); 23373 if (str && this.map[str]) { 23374 this.elements = this.elements.concat(this.map[str]); 23375 this.source.consume(i); 23376 return; 23377 } 23378 } 23379 23380 if (str) { 23381 // no mappings for the first code point, so just use its 23382 // Unicode code point as a proxy for its sort order. Shift 23383 // it by the key size so that everything unknown sorts 23384 // after things that have mappings 23385 this.elements.push(str.charCodeAt(0) << this.keysize); 23386 this.source.consume(1); 23387 } else { 23388 // end of the string 23389 return undefined; 23390 } 23391 }; 23392 23393 /** 23394 * Return true if there are more collation elements left to 23395 * iterate through. 23396 * @returns {boolean} true if there are more elements left to 23397 * iterate through, and false otherwise 23398 */ 23399 ElementIterator.prototype.hasNext = function () { 23400 if (this.elements.length < 1) { 23401 this._fillBuffer(); 23402 } 23403 return !!this.elements.length; 23404 }; 23405 23406 /** 23407 * Return the next collation element. If more than one collation 23408 * element is generated from a sequence of code points 23409 * (ie. an "expansion"), then this class will buffer the 23410 * other elements and return them on subsequent calls to 23411 * this method. 23412 * 23413 * @returns {number|undefined} the next collation element or 23414 * undefined for no more collation elements 23415 */ 23416 ElementIterator.prototype.next = function () { 23417 if (this.elements.length < 1) { 23418 this._fillBuffer(); 23419 } 23420 var ret = this.elements[0]; 23421 this.elements = this.elements.slice(1); 23422 return ret; 23423 }; 23424 23425 23426 23427 /*< Collator.js */ 23428 /* 23429 * Collator.js - Collation routines 23430 * 23431 * Copyright © 2013-2015, JEDLSoft 23432 * 23433 * Licensed under the Apache License, Version 2.0 (the "License"); 23434 * you may not use this file except in compliance with the License. 23435 * You may obtain a copy of the License at 23436 * 23437 * http://www.apache.org/licenses/LICENSE-2.0 23438 * 23439 * Unless required by applicable law or agreed to in writing, software 23440 * distributed under the License is distributed on an "AS IS" BASIS, 23441 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23442 * 23443 * See the License for the specific language governing permissions and 23444 * limitations under the License. 23445 */ 23446 23447 /* !depends 23448 Locale.js 23449 ilib.js 23450 INumber.js 23451 isPunct.js 23452 NormString.js 23453 MathUtils.js 23454 Utils.js 23455 JSUtils.js 23456 LocaleInfo.js 23457 CodePointSource.js 23458 ElementIterator.js 23459 */ 23460 23461 // !data collation 23462 23463 23464 /** 23465 * @class 23466 * A class that implements a locale-sensitive comparator function 23467 * for use with sorting function. The comparator function 23468 * assumes that the strings it is comparing contain Unicode characters 23469 * encoded in UTF-16.<p> 23470 * 23471 * Collations usually depend only on the language, because most collation orders 23472 * are shared between locales that speak the same language. There are, however, a 23473 * number of instances where a locale collates differently than other locales 23474 * that share the same language. There are also a number of instances where a 23475 * locale collates differently based on the script used. This object can handle 23476 * these cases automatically if a full locale is specified in the options rather 23477 * than just a language code.<p> 23478 * 23479 * <h2>Options</h2> 23480 * 23481 * The options parameter can contain any of the following properties: 23482 * 23483 * <ul> 23484 * <li><i>locale</i> - String|Locale. The locale which the comparator function 23485 * will collate with. Default: the current iLib locale. 23486 * 23487 * <li><i>sensitivity</i> - String. Sensitivity or strength of collator. This is one of 23488 * "primary", "base", "secondary", "accent", "tertiary", "case", "quaternary", or 23489 * "variant". Default: "primary" 23490 * <ol> 23491 * <li>base or primary - Only the primary distinctions between characters are significant. 23492 * Another way of saying that is that the collator will be case-, accent-, and 23493 * variation-insensitive, and only distinguish between the base characters 23494 * <li>case or secondary - Both the primary and secondary distinctions between characters 23495 * are significant. That is, the collator will be accent- and variation-insensitive 23496 * and will distinguish between base characters and character case. 23497 * <li>accent or tertiary - The primary, secondary, and tertiary distinctions between 23498 * characters are all significant. That is, the collator will be 23499 * variation-insensitive, but accent-, case-, and base-character-sensitive. 23500 * <li>variant or quaternary - All distinctions between characters are significant. That is, 23501 * the algorithm is base character-, case-, accent-, and variation-sensitive. 23502 * </ol> 23503 * 23504 * <li><i>upperFirst</i> - boolean. When collating case-sensitively in a script that 23505 * has the concept of case, put upper-case 23506 * characters first, otherwise lower-case will come first. Warning: some browsers do 23507 * not implement this feature or at least do not implement it properly, so if you are 23508 * using the native collator with this option, you may get different results in different 23509 * browsers. To guarantee the same results, set useNative to false to use the ilib 23510 * collator implementation. This of course will be somewhat slower, but more 23511 * predictable. Default: true 23512 * 23513 * <li><i>reverse</i> - boolean. Return the list sorted in reverse order. When the 23514 * upperFirst option is also set to true, upper-case characters would then come at 23515 * the end of the list. Default: false. 23516 * 23517 * <li><i>scriptOrder</i> - string. When collating strings in multiple scripts, 23518 * this property specifies what order those scripts should be sorted. The default 23519 * Unicode Collation Algorithm (UCA) already has a default order for scripts, but 23520 * this can be tailored via this property. The value of this option is a 23521 * space-separated list of ISO 15924 scripts codes. If a code is specified in this 23522 * property, its default data must be included using the JS assembly tool. If the 23523 * data is not included, the ordering for the script will be ignored. Default: 23524 * the default order defined by the UCA. 23525 * 23526 * <li><i>style</i> - The value of the style parameter is dependent on the locale. 23527 * For some locales, there are different styles of collating strings depending 23528 * on what kind of strings are being collated or what the preference of the user 23529 * is. For example, in German, there is a phonebook order and a dictionary ordering 23530 * that sort the same array of strings slightly differently. 23531 * The static method {@link Collator#getAvailableStyles} will return a list of styles that ilib 23532 * currently knows about for any given locale. If the value of the style option is 23533 * not recognized for a locale, it will be ignored. Default style is "standard".<p> 23534 * 23535 * <li><i>usage</i> - Whether this collator will be used for searching or sorting. 23536 * Valid values are simply the strings "sort" or "search". When used for sorting, 23537 * it is good idea if a collator produces a stable sort. That is, the order of the 23538 * sorted array of strings should not depend on the order of the strings in the 23539 * input array. As such, when a collator is supposed to act case insensitively, 23540 * it nonetheless still distinguishes between case after all other criteria 23541 * are satisfied so that strings that are distinguished only by case do not sort 23542 * randomly. For searching, we would like to match two strings that different only 23543 * by case, so the collator must return equals in that situation instead of 23544 * further distinguishing by case. Default is "sort". 23545 * 23546 * <li><i>numeric</i> - Treat the left and right strings as if they started with 23547 * numbers and sort them numerically rather than lexically. 23548 * 23549 * <li><i>ignorePunctuation</i> - Skip punctuation characters when comparing the 23550 * strings. 23551 * 23552 * <li>onLoad - a callback function to call when the collator object is fully 23553 * loaded. When the onLoad option is given, the collator object will attempt to 23554 * load any missing locale data using the ilib loader callback. 23555 * When the constructor is done (even if the data is already preassembled), the 23556 * onLoad function is called with the current instance as a parameter, so this 23557 * callback can be used with preassembled or dynamic loading or a mix of the two. 23558 * 23559 * <li>sync - tell whether to load any missing locale data synchronously or 23560 * asynchronously. If this option is given as "false", then the "onLoad" 23561 * callback must be given, as the instance returned from this constructor will 23562 * not be usable for a while. 23563 * 23564 * <li><i>loadParams</i> - an object containing parameters to pass to the 23565 * loader callback function when locale data is missing. The parameters are not 23566 * interpretted or modified in any way. They are simply passed along. The object 23567 * may contain any property/value pairs as long as the calling code is in 23568 * agreement with the loader callback function as to what those parameters mean. 23569 * 23570 * <li><i>useNative</i> - when this option is true, use the native Intl object 23571 * provided by the Javascript engine, if it exists, to implement this class. If 23572 * it doesn't exist, or if this parameter is false, then this class uses a pure 23573 * Javascript implementation, which is slower and uses a lot more memory, but 23574 * works everywhere that ilib works. Default is "true". 23575 * </ul> 23576 * 23577 * <h2>Operation</h2> 23578 * 23579 * The Collator constructor returns a collator object tailored with the above 23580 * options. The object contains an internal compare() method which compares two 23581 * strings according to those options. This can be used directly to compare 23582 * two strings, but is not useful for passing to the javascript sort function 23583 * because then it will not have its collation data available. Instead, use the 23584 * getComparator() method to retrieve a function that is bound to the collator 23585 * object. (You could also bind it yourself using ilib.bind()). The bound function 23586 * can be used with the standard Javascript array sorting algorithm, or as a 23587 * comparator with your own sorting algorithm.<p> 23588 * 23589 * Example using the standard Javascript array sorting call with the bound 23590 * function:<p> 23591 * 23592 * <code> 23593 * <pre> 23594 * var arr = ["ö", "oe", "ü", "o", "a", "ae", "u", "ß", "ä"]; 23595 * var collator = new Collator({locale: 'de-DE', style: "dictionary"}); 23596 * arr.sort(collator.getComparator()); 23597 * console.log(JSON.stringify(arr)); 23598 * </pre> 23599 * </code> 23600 * <p> 23601 * 23602 * Would give the output:<p> 23603 * 23604 * <code> 23605 * <pre> 23606 * ["a", "ae", "ä", "o", "oe", "ö", "ß", "u", "ü"] 23607 * </pre> 23608 * </code> 23609 * 23610 * When sorting an array of Javascript objects according to one of the 23611 * string properties of the objects, wrap the collator's compare function 23612 * in your own comparator function that knows the structure of the objects 23613 * being sorted:<p> 23614 * 23615 * <code> 23616 * <pre> 23617 * var collator = new Collator({locale: 'de-DE'}); 23618 * var myComparator = function (collator) { 23619 * var comparator = collator.getComparator(); 23620 * // left and right are your own objects 23621 * return function (left, right) { 23622 * return comparator(left.x.y.textProperty, right.x.y.textProperty); 23623 * }; 23624 * }; 23625 * arr.sort(myComparator(collator)); 23626 * </pre> 23627 * </code> 23628 * <p> 23629 * 23630 * <h2>Sort Keys</h2> 23631 * 23632 * The collator class also has a method to retrieve the sort key for a 23633 * string. The sort key is an array of values that represent how each 23634 * character in the string should be collated according to the characteristics 23635 * of the collation algorithm and the given options. Thus, sort keys can be 23636 * compared directly value-for-value with other sort keys that were generated 23637 * by the same collator, and the resulting ordering is guaranteed to be the 23638 * same as if the original strings were compared by the collator. 23639 * Sort keys generated by different collators are not guaranteed to give 23640 * any reasonable results when compared together unless the two collators 23641 * were constructed with 23642 * exactly the same options and therefore end up representing the exact same 23643 * collation sequence.<p> 23644 * 23645 * A good rule of thumb is that you would use a sort key if you had 10 or more 23646 * items to sort or if your array might be resorted arbitrarily. For example, if your 23647 * user interface was displaying a table with 100 rows in it, and each row had 23648 * 4 sortable text columns which could be sorted in acending or descending order, 23649 * the recommended practice would be to generate a sort key for each of the 4 23650 * sortable fields in each row and store that in the Javascript representation of the 23651 * table data. Then, when the user clicks on a column header to resort the 23652 * table according to that column, the resorting would be relatively quick 23653 * because it would only be comparing arrays of values, and not recalculating 23654 * the collation values for each character in each string for every comparison.<p> 23655 * 23656 * For tables that are large, it is usually a better idea to do the sorting 23657 * on the server side, especially if the table is the result of a database 23658 * query. In this case, the table is usually a view of the cursor of a large 23659 * results set, and only a few entries are sent to the front end at a time. 23660 * In order to sort the set efficiently, it should be done on the database 23661 * level instead. 23662 * 23663 * <h2>Data</h2> 23664 * 23665 * Doing correct collation entails a huge amount of mapping data, much of which is 23666 * not necessary when collating in one language with one script, which is the most 23667 * common case. Thus, ilib implements a number of ways to include the data you 23668 * need or leave out the data you don't need using the JS assembly tool: 23669 * 23670 * <ol> 23671 * <li>Full multilingual data - if you are sorting multilingual data and need to collate 23672 * text written in multiple scripts, you can use the directive "!data collation/ducet" to 23673 * load in the full collation data. This allows the collator to perform the entire 23674 * Unicode Collation Algorithm (UCA) based on the Default Unicode Collation Element 23675 * Table (DUCET). The data is very large, on the order of multiple megabytes, but 23676 * sometimes it is necessary. 23677 * <li>A few scripts - if you are sorting text written in only a few scripts, you may 23678 * want to include only the data for those scripts. Each ISO 15924 script code has its 23679 * own data available in a separate file, so you can use the data directive to include 23680 * only the data for the scripts you need. For example, use 23681 * "!data collation/Latn" to retrieve the collation information for the Latin script. 23682 * Because the "ducet" table mentioned in the previous point is a superset of the 23683 * tables for all other scripts, you do not need to include explicitly the data for 23684 * any particular script when using "ducet". That is, you either include "ducet" or 23685 * you include a specific list of scripts. 23686 * <li>Only one script - if you are sorting text written only in one script, you can 23687 * either include the data directly as in the previous point, or you can rely on the 23688 * locale to include the correct data for you. In this case, you can use the directive 23689 * "!data collate" to load in the locale's collation data for its most common script. 23690 * </ol> 23691 * 23692 * With any of the above ways of including the data, the collator will only perform the 23693 * correct language-sensitive sorting for the given locale. All other scripts will be 23694 * sorted in the default manner according to the UCA. For example, if you include the 23695 * "ducet" data and pass in "de-DE" (German for Germany) as the locale spec, then 23696 * only the Latin script (the default script for German) will be sorted according to 23697 * German rules. All other scripts in the DUCET, such as Japanese or Arabic, will use 23698 * the default UCA collation rules.<p> 23699 * 23700 * If this collator encounters a character for which it has no collation data, it will 23701 * sort those characters by pure Unicode value after all characters for which it does have 23702 * collation data. For example, if you only loaded in the German collation data (ie. the 23703 * data for the Latin script tailored to German) to sort a list of person names, but that 23704 * list happens to include the names of a few Japanese people written in Japanese 23705 * characters, the Japanese names will sort at the end of the list after all German names, 23706 * and will sort according to the Unicode values of the characters. 23707 * 23708 * @constructor 23709 * @param {Object} options options governing how the resulting comparator 23710 * function will operate 23711 */ 23712 var Collator = function(options) { 23713 var sync = true, 23714 loadParams = undefined, 23715 useNative = true; 23716 23717 // defaults 23718 /** 23719 * @private 23720 * @type {Locale} 23721 */ 23722 this.locale = new Locale(ilib.getLocale()); 23723 23724 /** @private */ 23725 this.caseFirst = "upper"; 23726 /** @private */ 23727 this.sensitivity = "variant"; 23728 /** @private */ 23729 this.level = 4; 23730 /** @private */ 23731 this.usage = "sort"; 23732 /** @private */ 23733 this.reverse = false; 23734 /** @private */ 23735 this.numeric = false; 23736 /** @private */ 23737 this.style = "default"; 23738 /** @private */ 23739 this.ignorePunctuation = false; 23740 23741 if (options) { 23742 if (options.locale) { 23743 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 23744 } 23745 if (options.sensitivity) { 23746 switch (options.sensitivity) { 23747 case 'primary': 23748 case 'base': 23749 this.sensitivity = "base"; 23750 this.level = 1; 23751 break; 23752 case 'secondary': 23753 case 'accent': 23754 this.sensitivity = "accent"; 23755 this.level = 2; 23756 break; 23757 case 'tertiary': 23758 case 'case': 23759 this.sensitivity = "case"; 23760 this.level = 3; 23761 break; 23762 case 'quaternary': 23763 case 'variant': 23764 this.sensitivity = "variant"; 23765 this.level = 4; 23766 break; 23767 } 23768 } 23769 if (typeof(options.upperFirst) !== 'undefined') { 23770 this.caseFirst = options.upperFirst ? "upper" : "lower"; 23771 } 23772 23773 if (typeof(options.ignorePunctuation) !== 'undefined') { 23774 this.ignorePunctuation = options.ignorePunctuation; 23775 } 23776 if (typeof(options.sync) !== 'undefined') { 23777 sync = (options.sync == true); 23778 } 23779 23780 loadParams = options.loadParams; 23781 if (typeof(options.useNative) !== 'undefined') { 23782 useNative = options.useNative; 23783 } 23784 23785 if (options.usage === "sort" || options.usage === "search") { 23786 this.usage = options.usage; 23787 } 23788 23789 if (typeof(options.reverse) === 'boolean') { 23790 this.reverse = options.reverse; 23791 } 23792 23793 if (typeof(options.numeric) === 'boolean') { 23794 this.numeric = options.numeric; 23795 } 23796 23797 if (typeof(options.style) === 'string') { 23798 this.style = options.style; 23799 } 23800 } 23801 23802 if (this.usage === "sort") { 23803 // produces a stable sort 23804 this.level = 4; 23805 } 23806 23807 if (useNative && typeof(Intl) !== 'undefined' && Intl) { 23808 // this engine is modern and supports the new Intl object! 23809 //console.log("implemented natively"); 23810 /** 23811 * @private 23812 * @type {{compare:function(string,string)}} 23813 */ 23814 this.collator = new Intl.Collator(this.locale.getSpec(), { 23815 sensitivity: this.sensitivity, 23816 caseFirst: this.caseFirst, 23817 ignorePunctuation: this.ignorePunctuation, 23818 numeric: this.numeric, 23819 usage: this.usage 23820 }); 23821 23822 if (options && typeof(options.onLoad) === 'function') { 23823 options.onLoad(this); 23824 } 23825 } else { 23826 //console.log("implemented in pure JS"); 23827 if (!Collator.cache) { 23828 Collator.cache = {}; 23829 } 23830 23831 // else implement in pure Javascript 23832 Utils.loadData({ 23833 object: Collator, 23834 locale: this.locale, 23835 name: "collation.json", 23836 sync: sync, 23837 loadParams: loadParams, 23838 callback: ilib.bind(this, function (collation) { 23839 if (!collation) { 23840 collation = ilib.data.collation; 23841 var spec = this.locale.getSpec().replace(/-/g, '_'); 23842 Collator.cache[spec] = collation; 23843 } 23844 this._init(collation); 23845 new LocaleInfo(this.locale, { 23846 sync: sync, 23847 loadParams: loadParams, 23848 onLoad: ilib.bind(this, function(li) { 23849 this.li = li; 23850 if (this.ignorePunctuation) { 23851 isPunct._init(sync, loadParams, ilib.bind(this, function() { 23852 if (options && typeof(options.onLoad) === 'function') { 23853 options.onLoad(this); 23854 } 23855 })); 23856 } else { 23857 if (options && typeof(options.onLoad) === 'function') { 23858 options.onLoad(this); 23859 } 23860 } 23861 }) 23862 }); 23863 }) 23864 }); 23865 } 23866 }; 23867 23868 Collator.prototype = { 23869 /** 23870 * @private 23871 * Bit pack an array of values into a single number 23872 * @param {number|null|Array.<number>} arr array of values to bit pack 23873 * @param {number} offset offset for the start of this map 23874 */ 23875 _pack: function (arr, offset) { 23876 var value = 0; 23877 if (arr) { 23878 if (typeof(arr) === 'number') { 23879 arr = [ arr ]; 23880 } 23881 for (var i = 0; i < this.level; i++) { 23882 var thisLevel = (typeof(arr[i]) !== "undefined" ? arr[i] : 0); 23883 if (i === 0) { 23884 thisLevel += offset; 23885 } 23886 if (i > 0) { 23887 value <<= this.collation.bits[i]; 23888 } 23889 if (i === 2 && this.caseFirst === "lower") { 23890 // sort the lower case first instead of upper 23891 value = value | (1 - thisLevel); 23892 } else { 23893 value = value | thisLevel; 23894 } 23895 } 23896 } 23897 return value; 23898 }, 23899 23900 /** 23901 * @private 23902 * Return the rule packed into an array of collation elements. 23903 * @param {Array.<number|null|Array.<number>>} rule 23904 * @param {number} offset 23905 * @return {Array.<number>} a bit-packed array of numbers 23906 */ 23907 _packRule: function(rule, offset) { 23908 if (ilib.isArray(rule[0])) { 23909 var ret = []; 23910 for (var i = 0; i < rule.length; i++) { 23911 ret.push(this._pack(rule[i], offset)); 23912 } 23913 return ret; 23914 } else { 23915 return [ this._pack(rule, offset) ]; 23916 } 23917 }, 23918 23919 /** 23920 * @private 23921 */ 23922 _addChars: function (str, offset) { 23923 var gs = new GlyphString(str); 23924 var it = gs.charIterator(); 23925 var c; 23926 23927 while (it.hasNext()) { 23928 c = it.next(); 23929 if (c === "'") { 23930 // escape a sequence of chars as one collation element 23931 c = ""; 23932 var x = ""; 23933 while (it.hasNext() && x !== "'") { 23934 c += x; 23935 x = it.next(); 23936 } 23937 } 23938 this.lastMap++; 23939 this.map[c] = this._packRule([this.lastMap], offset); 23940 } 23941 }, 23942 23943 /** 23944 * @private 23945 */ 23946 _addRules: function(rules, start) { 23947 var p; 23948 for (var r in rules.map) { 23949 if (r) { 23950 this.map[r] = this._packRule(rules.map[r], start); 23951 p = typeof(rules.map[r][0]) === 'number' ? rules.map[r][0] : rules.map[r][0][0]; 23952 this.lastMap = Math.max(p + start, this.lastMap); 23953 } 23954 } 23955 23956 if (typeof(rules.ranges) !== 'undefined') { 23957 // for each range, everything in the range goes in primary sequence from the start 23958 for (var i = 0; i < rules.ranges.length; i++) { 23959 var range = rules.ranges[i]; 23960 23961 this.lastMap = range.start; 23962 if (typeof(range.chars) === "string") { 23963 this._addChars(range.chars, start); 23964 } else { 23965 for (var k = 0; k < range.chars.length; k++) { 23966 this._addChars(range.chars[k], start); 23967 } 23968 } 23969 } 23970 } 23971 }, 23972 23973 /** 23974 * @private 23975 */ 23976 _init: function(rules) { 23977 var rule = this.style; 23978 while (typeof(rule) === 'string') { 23979 rule = rules[rule]; 23980 } 23981 if (!rule) { 23982 rule = "default"; 23983 while (typeof(rule) === 'string') { 23984 rule = rules[rule]; 23985 } 23986 } 23987 if (!rule) { 23988 this.map = {}; 23989 return; 23990 } 23991 23992 /** 23993 * @private 23994 * @type {{scripts:Array.<string>,bits:Array.<number>,maxes:Array.<number>,bases:Array.<number>,map:Object.<string,Array.<number|null|Array.<number>>>}} 23995 */ 23996 this.collation = rule; 23997 this.map = {}; 23998 this.lastMap = -1; 23999 this.keysize = this.collation.keysize[this.level-1]; 24000 24001 if (typeof(this.collation.inherit) !== 'undefined') { 24002 for (var i = 0; i < this.collation.inherit.length; i++) { 24003 var col = this.collation.inherit[i]; 24004 rule = typeof(col) === 'object' ? col.name : col; 24005 if (rules[rule]) { 24006 this._addRules(rules[rule], col.start || this.lastMap+1); 24007 } 24008 } 24009 } 24010 this._addRules(this.collation, this.lastMap+1); 24011 }, 24012 24013 /** 24014 * @private 24015 */ 24016 _basicCompare: function(left, right) { 24017 var l = (left instanceof NormString) ? left : new NormString(left), 24018 r = (right instanceof NormString) ? right : new NormString(right), 24019 lchar, 24020 rchar, 24021 lelements, 24022 relements; 24023 24024 if (this.numeric) { 24025 var lvalue = new INumber(left, {locale: this.locale}); 24026 var rvalue = new INumber(right, {locale: this.locale}); 24027 if (!isNaN(lvalue.valueOf()) && !isNaN(rvalue.valueOf())) { 24028 var diff = lvalue.valueOf() - rvalue.valueOf(); 24029 if (diff) { 24030 return diff; 24031 } else { 24032 // skip the numeric part and compare the rest lexically 24033 l = new NormString(left.substring(lvalue.parsed.length)); 24034 r = new NormString(right.substring(rvalue.parsed.length)); 24035 } 24036 } 24037 // else if they aren't both numbers, then let the code below take care of the lexical comparison instead 24038 } 24039 24040 lelements = new ElementIterator(new CodePointSource(l, this.ignorePunctuation), this.map, this.keysize); 24041 relements = new ElementIterator(new CodePointSource(r, this.ignorePunctuation), this.map, this.keysize); 24042 24043 while (lelements.hasNext() && relements.hasNext()) { 24044 var diff = lelements.next() - relements.next(); 24045 if (diff) { 24046 return diff; 24047 } 24048 } 24049 if (!lelements.hasNext() && !relements.hasNext()) { 24050 return 0; 24051 } else if (lelements.hasNext()) { 24052 return 1; 24053 } else { 24054 return -1; 24055 } 24056 }, 24057 24058 /** 24059 * Compare two strings together according to the rules of this 24060 * collator instance. Do not use this function directly with 24061 * Array.sort, as it will not have its collation data available 24062 * and therefore will not function properly. Use the function 24063 * returned by getComparator() instead. 24064 * 24065 * @param {string} left the left string to compare 24066 * @param {string} right the right string to compare 24067 * @return {number} a negative number if left comes before right, a 24068 * positive number if right comes before left, and zero if left and 24069 * right are equivalent according to this collator 24070 */ 24071 compare: function (left, right) { 24072 // last resort: use the "C" locale 24073 if (this.collator) { 24074 // implemented by the core engine 24075 return this.collator.compare(left, right); 24076 } 24077 24078 var ret = this._basicCompare(left, right); 24079 return this.reverse ? -ret : ret; 24080 }, 24081 24082 /** 24083 * Return a comparator function that can compare two strings together 24084 * according to the rules of this collator instance. The function 24085 * returns a negative number if the left 24086 * string comes before right, a positive number if the right string comes 24087 * before the left, and zero if left and right are equivalent. If the 24088 * reverse property was given as true to the collator constructor, this 24089 * function will 24090 * switch the sign of those values to cause sorting to happen in the 24091 * reverse order. 24092 * 24093 * @return {function(...)|undefined} a comparator function that 24094 * can compare two strings together according to the rules of this 24095 * collator instance 24096 */ 24097 getComparator: function() { 24098 // bind the function to this instance so that we have the collation 24099 // rules available to do the work 24100 if (this.collator) { 24101 // implemented by the core engine 24102 return this.collator.compare; 24103 } 24104 24105 return ilib.bind(this, this.compare); 24106 }, 24107 24108 /** 24109 * Return a sort key string for the given string. The sort key 24110 * string is a list of values that represent each character 24111 * in the original string. The sort key 24112 * values for any particular character consists of 3 numbers that 24113 * encode the primary, secondary, and tertiary characteristics 24114 * of that character. The values of each characteristic are 24115 * modified according to the strength of this collator instance 24116 * to give the correct collation order. The idea is that this 24117 * sort key string is directly comparable byte-for-byte to 24118 * other sort key strings generated by this collator without 24119 * any further knowledge of the collation rules for the locale. 24120 * More formally, if a < b according to the rules of this collation, 24121 * then it is guaranteed that sortkey(a) < sortkey(b) when compared 24122 * byte-for-byte. The sort key string can therefore be used 24123 * without the collator to sort an array of strings efficiently 24124 * because the work of determining the applicability of various 24125 * collation rules is done once up-front when generating 24126 * the sort key.<p> 24127 * 24128 * The sort key string can be treated as a regular, albeit somewhat 24129 * odd-looking, string. That is, it can be pass to regular 24130 * Javascript functions without problems. 24131 * 24132 * @param {string} str the original string to generate the sort key for 24133 * @return {string} a sort key string for the given string 24134 */ 24135 sortKey: function (str) { 24136 if (!str) { 24137 return ""; 24138 } 24139 24140 if (this.collator) { 24141 // native, no sort keys available 24142 return str; 24143 } 24144 24145 if (this.numeric) { 24146 var v = new INumber(str, {locale: this.locale}); 24147 var s = isNaN(v.valueOf()) ? "" : v.valueOf().toString(16); 24148 return JSUtils.pad(s, 16); 24149 } else { 24150 var n = (typeof(str) === "string") ? new NormString(str) : str, 24151 ret = "", 24152 lelements = new ElementIterator(new CodePointSource(n, this.ignorePunctuation), this.map, this.keysize), 24153 element; 24154 24155 while (lelements.hasNext()) { 24156 element = lelements.next(); 24157 if (this.reverse) { 24158 // for reverse, take the bitwise inverse 24159 element = (1 << this.keysize) - element; 24160 } 24161 ret += JSUtils.pad(element.toString(16), this.keysize/4); 24162 } 24163 } 24164 return ret; 24165 } 24166 }; 24167 24168 /** 24169 * Retrieve the list of collation style names that are available for the 24170 * given locale. This list varies depending on the locale, and depending 24171 * on whether or not the data for that locale was assembled into this copy 24172 * of ilib. 24173 * 24174 * @param {Locale|string=} locale The locale for which the available 24175 * styles are being sought 24176 * @return Array.<string> an array of style names that are available for 24177 * the given locale 24178 */ 24179 Collator.getAvailableStyles = function (locale) { 24180 return [ "standard" ]; 24181 }; 24182 24183 /** 24184 * Retrieve the list of ISO 15924 script codes that are available in this 24185 * copy of ilib. This list varies depending on whether or not the data for 24186 * various scripts was assembled into this copy of ilib. If the "ducet" 24187 * data is assembled into this copy of ilib, this method will report the 24188 * entire list of scripts as being available. If a collator instance is 24189 * instantiated with a script code that is not on the list returned by this 24190 * function, it will be ignored and text in that script will be sorted by 24191 * numeric Unicode values of the characters. 24192 * 24193 * @return Array.<string> an array of ISO 15924 script codes that are 24194 * available 24195 */ 24196 Collator.getAvailableScripts = function () { 24197 return [ "Latn" ]; 24198 }; 24199 24200 24201 24202 /*< nfd/all.js */ 24203 /* 24204 * all.js - include file for normalization data for a particular script 24205 * 24206 * Copyright © 2013-2015, JEDLSoft 24207 * 24208 * Licensed under the Apache License, Version 2.0 (the "License"); 24209 * you may not use this file except in compliance with the License. 24210 * You may obtain a copy of the License at 24211 * 24212 * http://www.apache.org/licenses/LICENSE-2.0 24213 * 24214 * Unless required by applicable law or agreed to in writing, software 24215 * distributed under the License is distributed on an "AS IS" BASIS, 24216 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24217 * 24218 * See the License for the specific language governing permissions and 24219 * limitations under the License. 24220 */ 24221 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 24222 // !data normdata nfd/all 24223 ilib.extend(ilib.data.norm, ilib.data.normdata); 24224 ilib.extend(ilib.data.norm.nfd, ilib.data.nfd_all); 24225 ilib.data.normdata = undefined; 24226 ilib.data.nfd_all = undefined; 24227 /*< nfkd/all.js */ 24228 /* 24229 * all.js - include file for normalization data for a particular script 24230 * 24231 * Copyright © 2013-2015, JEDLSoft 24232 * 24233 * Licensed under the Apache License, Version 2.0 (the "License"); 24234 * you may not use this file except in compliance with the License. 24235 * You may obtain a copy of the License at 24236 * 24237 * http://www.apache.org/licenses/LICENSE-2.0 24238 * 24239 * Unless required by applicable law or agreed to in writing, software 24240 * distributed under the License is distributed on an "AS IS" BASIS, 24241 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24242 * 24243 * See the License for the specific language governing permissions and 24244 * limitations under the License. 24245 */ 24246 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 24247 // !depends nfd/all.js 24248 // !data normdata nfkd/all 24249 ilib.extend(ilib.data.norm, ilib.data.normdata); 24250 ilib.extend(ilib.data.norm.nfkd, ilib.data.nfkd_all); 24251 ilib.data.normdata = undefined; 24252 ilib.data.nfkd_all = undefined; 24253 /*< nfkc/all.js */ 24254 /* 24255 * all.js - include file for normalization data for a particular script 24256 * 24257 * Copyright © 2013-2015, JEDLSoft 24258 * 24259 * Licensed under the Apache License, Version 2.0 (the "License"); 24260 * you may not use this file except in compliance with the License. 24261 * You may obtain a copy of the License at 24262 * 24263 * http://www.apache.org/licenses/LICENSE-2.0 24264 * 24265 * Unless required by applicable law or agreed to in writing, software 24266 * distributed under the License is distributed on an "AS IS" BASIS, 24267 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24268 * 24269 * See the License for the specific language governing permissions and 24270 * limitations under the License. 24271 */ 24272 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 24273 // !depends nfd/all.js nfkd/all.js 24274 // !data norm 24275 ilib.extend(ilib.data.norm, ilib.data.normdata); 24276 ilib.data.normdata = undefined; 24277 24278 /*< LocaleMatcher.js */ 24279 /* 24280 * LocaleMatcher.js - Locale matcher definition 24281 * 24282 * Copyright © 2013-2015, JEDLSoft 24283 * 24284 * Licensed under the Apache License, Version 2.0 (the "License"); 24285 * you may not use this file except in compliance with the License. 24286 * You may obtain a copy of the License at 24287 * 24288 * http://www.apache.org/licenses/LICENSE-2.0 24289 * 24290 * Unless required by applicable law or agreed to in writing, software 24291 * distributed under the License is distributed on an "AS IS" BASIS, 24292 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24293 * 24294 * See the License for the specific language governing permissions and 24295 * limitations under the License. 24296 */ 24297 24298 // !depends ilib.js Locale.js Utils.js 24299 // !data likelylocales 24300 24301 24302 /** 24303 * @class 24304 * Create a new locale matcher instance. This is used 24305 * to see which locales can be matched with each other in 24306 * various ways.<p> 24307 * 24308 * The options object may contain any of the following properties: 24309 * 24310 * <ul> 24311 * <li><i>locale</i> - the locale to match 24312 * 24313 * <li><i>onLoad</i> - a callback function to call when the locale matcher object is fully 24314 * loaded. When the onLoad option is given, the locale matcher object will attempt to 24315 * load any missing locale data using the ilib loader callback. 24316 * When the constructor is done (even if the data is already preassembled), the 24317 * onLoad function is called with the current instance as a parameter, so this 24318 * callback can be used with preassembled or dynamic loading or a mix of the two. 24319 * 24320 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 24321 * asynchronously. If this option is given as "false", then the "onLoad" 24322 * callback must be given, as the instance returned from this constructor will 24323 * not be usable for a while. 24324 * 24325 * <li><i>loadParams</i> - an object containing parameters to pass to the 24326 * loader callback function when locale data is missing. The parameters are not 24327 * interpretted or modified in any way. They are simply passed along. The object 24328 * may contain any property/value pairs as long as the calling code is in 24329 * agreement with the loader callback function as to what those parameters mean. 24330 * </ul> 24331 * 24332 * 24333 * @constructor 24334 * @param {Object} options parameters to initialize this matcher 24335 */ 24336 var LocaleMatcher = function(options) { 24337 var sync = true, 24338 loadParams = undefined; 24339 24340 this.locale = new Locale(); 24341 24342 if (options) { 24343 if (typeof(options.locale) !== 'undefined') { 24344 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24345 } 24346 24347 if (typeof(options.sync) !== 'undefined') { 24348 sync = (options.sync == true); 24349 } 24350 24351 if (typeof(options.loadParams) !== 'undefined') { 24352 loadParams = options.loadParams; 24353 } 24354 } 24355 24356 if (!LocaleMatcher.cache) { 24357 LocaleMatcher.cache = {}; 24358 } 24359 24360 if (typeof(ilib.data.likelylocales) === 'undefined') { 24361 Utils.loadData({ 24362 object: LocaleMatcher, 24363 locale: "-", 24364 name: "likelylocales.json", 24365 sync: sync, 24366 loadParams: loadParams, 24367 callback: ilib.bind(this, function (info) { 24368 if (!info) { 24369 info = {}; 24370 var spec = this.locale.getSpec().replace(/-/g, "_"); 24371 LocaleMatcher.cache[spec] = info; 24372 } 24373 /** @type {Object.<string,string>} */ 24374 this.info = info; 24375 if (options && typeof(options.onLoad) === 'function') { 24376 options.onLoad(this); 24377 } 24378 }) 24379 }); 24380 } else { 24381 this.info = ilib.data.likelylocales; 24382 } 24383 }; 24384 24385 24386 LocaleMatcher.prototype = { 24387 /** 24388 * Return the locale used to construct this instance. 24389 * @return {Locale|undefined} the locale for this matcher 24390 */ 24391 getLocale: function() { 24392 return this.locale; 24393 }, 24394 24395 /** 24396 * Return an Locale instance that is fully specified based on partial information 24397 * given to the constructor of this locale matcher instance. For example, if the locale 24398 * spec given to this locale matcher instance is simply "ru" (for the Russian language), 24399 * then it will fill in the missing region and script tags and return a locale with 24400 * the specifier "ru-Cyrl-RU". (ie. Russian language, Cyrillic, Russian Federation). 24401 * Any one or two of the language, script, or region parts may be left unspecified, 24402 * and the other one or two parts will be filled in automatically. If this 24403 * class has no information about the given locale, then the locale of this 24404 * locale matcher instance is returned unchanged. 24405 * 24406 * @returns {Locale} the most likely completion of the partial locale given 24407 * to the constructor of this locale matcher instance 24408 */ 24409 getLikelyLocale: function () { 24410 if (typeof(this.info[this.locale.getSpec()]) === 'undefined') { 24411 return this.locale; 24412 } 24413 24414 return new Locale(this.info[this.locale.getSpec()]); 24415 } 24416 }; 24417 24418 24419 /*< CaseMapper.js */ 24420 /* 24421 * caseMapper.js - define upper- and lower-case mapper 24422 * 24423 * Copyright © 2014-2015, JEDLSoft 24424 * 24425 * Licensed under the Apache License, Version 2.0 (the "License"); 24426 * you may not use this file except in compliance with the License. 24427 * You may obtain a copy of the License at 24428 * 24429 * http://www.apache.org/licenses/LICENSE-2.0 24430 * 24431 * Unless required by applicable law or agreed to in writing, software 24432 * distributed under the License is distributed on an "AS IS" BASIS, 24433 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24434 * 24435 * See the License for the specific language governing permissions and 24436 * limitations under the License. 24437 */ 24438 24439 // !depends Locale.js IString.js 24440 24441 24442 24443 /** 24444 * @class 24445 * Create a new string mapper instance that maps strings to upper or 24446 * lower case. This mapping will work for any string as characters 24447 * that have no case will be returned unchanged.<p> 24448 * 24449 * The options may contain any of the following properties: 24450 * 24451 * <ul> 24452 * <li><i>locale</i> - locale to use when loading the mapper. Some maps are 24453 * locale-dependent, and this locale selects the right one. Default if this is 24454 * not specified is the current locale. 24455 * 24456 * <li><i>direction</i> - "toupper" for upper-casing, or "tolower" for lower-casing. 24457 * Default if not specified is "toupper". 24458 * </ul> 24459 * 24460 * 24461 * @constructor 24462 * @param {Object=} options options to initialize this mapper 24463 */ 24464 var CaseMapper = function (options) { 24465 this.up = true; 24466 this.locale = new Locale(); 24467 24468 if (options) { 24469 if (typeof(options.locale) !== 'undefined') { 24470 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24471 } 24472 24473 this.up = (!options.direction || options.direction === "toupper"); 24474 } 24475 24476 this.mapData = this.up ? { 24477 "ß": "SS", // German 24478 'ΐ': 'Ι', // Greek 24479 'ά': 'Α', 24480 'έ': 'Ε', 24481 'ή': 'Η', 24482 'ί': 'Ι', 24483 'ΰ': 'Υ', 24484 'ϊ': 'Ι', 24485 'ϋ': 'Υ', 24486 'ό': 'Ο', 24487 'ύ': 'Υ', 24488 'ώ': 'Ω', 24489 'Ӏ': 'Ӏ', // Russian and slavic languages 24490 'ӏ': 'Ӏ' 24491 } : { 24492 'Ӏ': 'Ӏ' // Russian and slavic languages 24493 }; 24494 24495 switch (this.locale.getLanguage()) { 24496 case "az": 24497 case "tr": 24498 case "crh": 24499 case "kk": 24500 case "krc": 24501 case "tt": 24502 var lower = "iı"; 24503 var upper = "İI"; 24504 this._setUpMap(lower, upper); 24505 break; 24506 case "fr": 24507 if (this.up && this.locale.getRegion() !== "CA") { 24508 this._setUpMap("àáâãäçèéêëìíîïñòóôöùúûü", "AAAAACEEEEIIIINOOOOUUUU"); 24509 } 24510 break; 24511 } 24512 24513 if (ilib._getBrowser() === "ie") { 24514 // IE is missing these mappings for some reason 24515 if (this.up) { 24516 this.mapData['ς'] = 'Σ'; 24517 } 24518 this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic 24519 // Georgian Nuskhuri <-> Asomtavruli 24520 this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ"); 24521 } 24522 }; 24523 24524 CaseMapper.prototype = { 24525 /** 24526 * @private 24527 */ 24528 _charMapper: function(string) { 24529 if (!string) { 24530 return string; 24531 } 24532 var input = (typeof(string) === 'string') ? new IString(string) : string.toString(); 24533 var ret = ""; 24534 var it = input.charIterator(); 24535 var c; 24536 24537 while (it.hasNext()) { 24538 c = it.next(); 24539 if (!this.up && c === 'Σ') { 24540 if (it.hasNext()) { 24541 c = it.next(); 24542 var code = c.charCodeAt(0); 24543 // if the next char is not a greek letter, this is the end of the word so use the 24544 // final form of sigma. Otherwise, use the mid-word form. 24545 ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ'; 24546 ret += c.toLowerCase(); 24547 } else { 24548 // no next char means this is the end of the word, so use the final form of sigma 24549 ret += 'ς'; 24550 } 24551 } else { 24552 if (this.mapData[c]) { 24553 ret += this.mapData[c]; 24554 } else { 24555 ret += this.up ? c.toUpperCase() : c.toLowerCase(); 24556 } 24557 } 24558 } 24559 24560 return ret; 24561 }, 24562 24563 /** @private */ 24564 _setUpMap: function(lower, upper) { 24565 var from, to; 24566 if (this.up) { 24567 from = lower; 24568 to = upper; 24569 } else { 24570 from = upper; 24571 to = lower; 24572 } 24573 for (var i = 0; i < upper.length; i++) { 24574 this.mapData[from[i]] = to[i]; 24575 } 24576 }, 24577 24578 /** 24579 * Return the locale that this mapper was constructed with. 24580 * @returns {Locale} the locale that this mapper was constructed with 24581 */ 24582 getLocale: function () { 24583 return this.locale; 24584 }, 24585 24586 /** 24587 * Map a string to lower case in a locale-sensitive manner. 24588 * 24589 * @param {string|undefined} string 24590 * @return {string|undefined} 24591 */ 24592 map: function (string) { 24593 return this._charMapper(string); 24594 } 24595 }; 24596 24597 24598 /*< NumberingPlan.js */ 24599 /* 24600 * NumPlan.js - Represent a phone numbering plan. 24601 * 24602 * Copyright © 2014-2015, JEDLSoft 24603 * 24604 * Licensed under the Apache License, Version 2.0 (the "License"); 24605 * you may not use this file except in compliance with the License. 24606 * You may obtain a copy of the License at 24607 * 24608 * http://www.apache.org/licenses/LICENSE-2.0 24609 * 24610 * Unless required by applicable law or agreed to in writing, software 24611 * distributed under the License is distributed on an "AS IS" BASIS, 24612 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24613 * 24614 * See the License for the specific language governing permissions and 24615 * limitations under the License. 24616 */ 24617 24618 /* 24619 !depends 24620 ilib.js 24621 Locale.js 24622 Utils.js 24623 JSUtils.js 24624 */ 24625 24626 // !data numplan 24627 24628 24629 /** 24630 * @class 24631 * Create a numbering plan information instance for a particular country's plan.<p> 24632 * 24633 * The options may contain any of the following properties: 24634 * 24635 * <ul> 24636 * <li><i>locale</i> - locale for which the numbering plan is sought. This locale 24637 * will be mapped to the actual numbering plan, which may be shared amongst a 24638 * number of countries. 24639 * 24640 * <li>onLoad - a callback function to call when the date format object is fully 24641 * loaded. When the onLoad option is given, the DateFmt object will attempt to 24642 * load any missing locale data using the ilib loader callback. 24643 * When the constructor is done (even if the data is already preassembled), the 24644 * onLoad function is called with the current instance as a parameter, so this 24645 * callback can be used with preassembled or dynamic loading or a mix of the two. 24646 * 24647 * <li>sync - tell whether to load any missing locale data synchronously or 24648 * asynchronously. If this option is given as "false", then the "onLoad" 24649 * callback must be given, as the instance returned from this constructor will 24650 * not be usable for a while. 24651 * 24652 * <li><i>loadParams</i> - an object containing parameters to pass to the 24653 * loader callback function when locale data is missing. The parameters are not 24654 * interpretted or modified in any way. They are simply passed along. The object 24655 * may contain any property/value pairs as long as the calling code is in 24656 * agreement with the loader callback function as to what those parameters mean. 24657 * </ul> 24658 * 24659 * @private 24660 * @constructor 24661 * @param {Object} options options governing the way this plan is loaded 24662 */ 24663 var NumberingPlan = function (options) { 24664 var sync = true, 24665 loadParams = {}; 24666 24667 this.locale = new Locale(); 24668 24669 if (options) { 24670 if (options.locale) { 24671 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24672 } 24673 24674 if (typeof(options.sync) !== 'undefined') { 24675 sync = (options.sync == true); 24676 } 24677 24678 if (options.loadParams) { 24679 loadParams = options.loadParams; 24680 } 24681 } 24682 24683 Utils.loadData({ 24684 name: "numplan.json", 24685 object: NumberingPlan, 24686 locale: this.locale, 24687 sync: sync, 24688 loadParams: loadParams, 24689 callback: ilib.bind(this, function (npdata) { 24690 if (!npdata) { 24691 npdata = { 24692 "region": "XX", 24693 "skipTrunk": false, 24694 "trunkCode": "0", 24695 "iddCode": "00", 24696 "dialingPlan": "closed", 24697 "commonFormatChars": " ()-./", 24698 "fieldLengths": { 24699 "areaCode": 0, 24700 "cic": 0, 24701 "mobilePrefix": 0, 24702 "serviceCode": 0 24703 } 24704 }; 24705 } 24706 24707 /** 24708 * @type {{ 24709 * region:string, 24710 * skipTrunk:boolean, 24711 * trunkCode:string, 24712 * iddCode:string, 24713 * dialingPlan:string, 24714 * commonFormatChars:string, 24715 * fieldLengths:Object.<string,number>, 24716 * contextFree:boolean, 24717 * findExtensions:boolean, 24718 * trunkRequired:boolean, 24719 * extendedAreaCodes:boolean 24720 * }} 24721 */ 24722 this.npdata = npdata; 24723 if (options && typeof(options.onLoad) === 'function') { 24724 options.onLoad(this); 24725 } 24726 }) 24727 }); 24728 }; 24729 24730 NumberingPlan.prototype = { 24731 /** 24732 * Return the name of this plan. This may be different than the 24733 * name of the region because sometimes multiple countries share 24734 * the same plan. 24735 * @return {string} the name of the plan 24736 */ 24737 getName: function() { 24738 return this.npdata.region; 24739 }, 24740 24741 /** 24742 * Return the trunk code of the current plan as a string. 24743 * @return {string|undefined} the trunk code of the plan or 24744 * undefined if there is no trunk code in this plan 24745 */ 24746 getTrunkCode: function() { 24747 return this.npdata.trunkCode; 24748 }, 24749 24750 /** 24751 * Return the international direct dialing code of this plan. 24752 * @return {string} the IDD code of this plan 24753 */ 24754 getIDDCode: function() { 24755 return this.npdata.iddCode; 24756 }, 24757 24758 /** 24759 * Return the plan style for this plan. The plan style may be 24760 * one of: 24761 * 24762 * <ul> 24763 * <li>"open" - area codes may be left off if the caller is 24764 * dialing to another number within the same area code 24765 * <li>"closed" - the area code must always be specified, even 24766 * if calling another number within the same area code 24767 * </ul> 24768 * 24769 * @return {string} the plan style, "open" or "closed" 24770 */ 24771 getPlanStyle: function() { 24772 return this.npdata.dialingPlan; 24773 }, 24774 /** [Need Comment] 24775 * Return a contextFree 24776 * 24777 * @return {boolean} 24778 */ 24779 getContextFree: function() { 24780 return this.npdata.contextFree; 24781 }, 24782 /** [Need Comment] 24783 * Return a findExtensions 24784 * 24785 * @return {boolean} 24786 */ 24787 getFindExtensions: function() { 24788 return this.npdata.findExtensions; 24789 }, 24790 /** [Need Comment] 24791 * Return a skipTrunk 24792 * 24793 * @return {boolean} 24794 */ 24795 getSkipTrunk: function() { 24796 return this.npdata.skipTrunk; 24797 }, 24798 /** [Need Comment] 24799 * Return a skipTrunk 24800 * 24801 * @return {boolean} 24802 */ 24803 getTrunkRequired: function() { 24804 return this.npdata.trunkRequired; 24805 }, 24806 /** 24807 * Return true if this plan uses extended area codes. 24808 * @return {boolean} true if the plan uses extended area codes 24809 */ 24810 getExtendedAreaCode: function() { 24811 return this.npdata.extendedAreaCodes; 24812 }, 24813 /** 24814 * Return a string containing all of the common format characters 24815 * used to format numbers. 24816 * @return {string} the common format characters fused in this locale 24817 */ 24818 getCommonFormatChars: function() { 24819 return this.npdata.commonFormatChars; 24820 }, 24821 24822 /** 24823 * Return the length of the field with the given name. If the length 24824 * is returned as 0, this means it is variable length. 24825 * 24826 * @param {string} field name of the field for which the length is 24827 * being sought 24828 * @return {number} if positive, this gives the length of the given 24829 * field. If zero, the field is variable length. If negative, the 24830 * field is not known. 24831 */ 24832 getFieldLength: function (field) { 24833 var dataField = this.npdata.fieldLengths; 24834 24835 return dataField[field]; 24836 } 24837 }; 24838 24839 24840 /*< PhoneLocale.js */ 24841 /* 24842 * phoneloc.js - Represent a phone locale object. 24843 * 24844 * Copyright © 2014-2015, JEDLSoft 24845 * 24846 * Licensed under the Apache License, Version 2.0 (the "License"); 24847 * you may not use this file except in compliance with the License. 24848 * You may obtain a copy of the License at 24849 * 24850 * http://www.apache.org/licenses/LICENSE-2.0 24851 * 24852 * Unless required by applicable law or agreed to in writing, software 24853 * distributed under the License is distributed on an "AS IS" BASIS, 24854 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24855 * 24856 * See the License for the specific language governing permissions and 24857 * limitations under the License. 24858 */ 24859 24860 /* 24861 !depends 24862 ilib.js 24863 Locale.js 24864 Utils.js 24865 */ 24866 24867 // !data phoneloc 24868 24869 24870 /** 24871 * @class 24872 * Extension of the locale class that has extra methods to map various numbers 24873 * related to phone number parsing. 24874 * 24875 * @param {Object} options Options that govern how this phone locale works 24876 * 24877 * @private 24878 * @constructor 24879 * @extends Locale 24880 */ 24881 var PhoneLocale = function(options) { 24882 var region, 24883 mcc, 24884 cc, 24885 sync = true, 24886 loadParams = {}, 24887 locale; 24888 24889 locale = (options && options.locale) || ilib.getLocale(); 24890 24891 this.parent.call(this, locale); 24892 24893 region = this.region; 24894 24895 if (options) { 24896 if (typeof(options.mcc) !== 'undefined') { 24897 mcc = options.mcc; 24898 } 24899 24900 if (typeof(options.countryCode) !== 'undefined') { 24901 cc = options.countryCode; 24902 } 24903 24904 if (typeof(options.sync) !== 'undefined') { 24905 sync = (options.sync == true); 24906 } 24907 24908 if (options.loadParams) { 24909 loadParams = options.loadParams; 24910 } 24911 } 24912 24913 Utils.loadData({ 24914 name: "phoneloc.json", 24915 object: PhoneLocale, 24916 nonlocale: true, 24917 sync: sync, 24918 loadParams: loadParams, 24919 callback: ilib.bind(this, function (data) { 24920 /** @type {{mcc2reg:Object.<string,string>,cc2reg:Object.<string,string>,reg2cc:Object.<string,string>,area2reg:Object.<string,string>}} */ 24921 this.mappings = data; 24922 24923 if (typeof(mcc) !== 'undefined') { 24924 region = this.mappings.mcc2reg[mcc]; 24925 } 24926 24927 if (typeof(cc) !== 'undefined') { 24928 region = this.mappings.cc2reg[cc]; 24929 } 24930 24931 if (!region) { 24932 region = "XX"; 24933 } 24934 24935 this.region = this._normPhoneReg(region); 24936 this._genSpec(); 24937 24938 if (options && typeof(options.onLoad) === 'function') { 24939 options.onLoad(this); 24940 } 24941 }) 24942 }); 24943 }; 24944 24945 PhoneLocale.prototype = new Locale(); 24946 PhoneLocale.prototype.parent = Locale; 24947 PhoneLocale.prototype.constructor = PhoneLocale; 24948 24949 /** 24950 * Map a mobile carrier code to a region code. 24951 * 24952 * @static 24953 * @package 24954 * @param {string|undefined} mcc the MCC to map 24955 * @return {string|undefined} the region code 24956 */ 24957 24958 PhoneLocale.prototype._mapMCCtoRegion = function(mcc) { 24959 if (!mcc) { 24960 return undefined; 24961 } 24962 return this.mappings.mcc2reg && this.mappings.mcc2reg[mcc] || "XX"; 24963 }; 24964 24965 /** 24966 * Map a country code to a region code. 24967 * 24968 * @static 24969 * @package 24970 * @param {string|undefined} cc the country code to map 24971 * @return {string|undefined} the region code 24972 */ 24973 PhoneLocale.prototype._mapCCtoRegion = function(cc) { 24974 if (!cc) { 24975 return undefined; 24976 } 24977 return this.mappings.cc2reg && this.mappings.cc2reg[cc] || "XX"; 24978 }; 24979 24980 /** 24981 * Map a region code to a country code. 24982 * 24983 * @static 24984 * @package 24985 * @param {string|undefined} region the region code to map 24986 * @return {string|undefined} the country code 24987 */ 24988 PhoneLocale.prototype._mapRegiontoCC = function(region) { 24989 if (!region) { 24990 return undefined; 24991 } 24992 return this.mappings.reg2cc && this.mappings.reg2cc[region] || "0"; 24993 }; 24994 24995 /** 24996 * Map a country code to a region code. 24997 * 24998 * @static 24999 * @package 25000 * @param {string|undefined} cc the country code to map 25001 * @param {string|undefined} area the area code within the country code's numbering plan 25002 * @return {string|undefined} the region code 25003 */ 25004 PhoneLocale.prototype._mapAreatoRegion = function(cc, area) { 25005 if (!cc) { 25006 return undefined; 25007 } 25008 if (cc in this.mappings.area2reg) { 25009 return this.mappings.area2reg[cc][area] || this.mappings.area2reg[cc]["default"]; 25010 } else { 25011 return this.mappings.cc2reg[cc]; 25012 } 25013 }; 25014 25015 /** 25016 * Return the region that controls the dialing plan in the given 25017 * region. (ie. the "normalized phone region".) 25018 * 25019 * @static 25020 * @package 25021 * @param {string} region the region code to normalize 25022 * @return {string} the normalized region code 25023 */ 25024 PhoneLocale.prototype._normPhoneReg = function(region) { 25025 var norm; 25026 25027 // Map all NANP regions to the right region, so that they get parsed using the 25028 // correct state table 25029 switch (region) { 25030 case "US": // usa 25031 case "CA": // canada 25032 case "AG": // antigua and barbuda 25033 case "BS": // bahamas 25034 case "BB": // barbados 25035 case "DM": // dominica 25036 case "DO": // dominican republic 25037 case "GD": // grenada 25038 case "JM": // jamaica 25039 case "KN": // st. kitts and nevis 25040 case "LC": // st. lucia 25041 case "VC": // st. vincent and the grenadines 25042 case "TT": // trinidad and tobago 25043 case "AI": // anguilla 25044 case "BM": // bermuda 25045 case "VG": // british virgin islands 25046 case "KY": // cayman islands 25047 case "MS": // montserrat 25048 case "TC": // turks and caicos 25049 case "AS": // American Samoa 25050 case "VI": // Virgin Islands, U.S. 25051 case "PR": // Puerto Rico 25052 case "MP": // Northern Mariana Islands 25053 case "T:": // East Timor 25054 case "GU": // Guam 25055 norm = "US"; 25056 break; 25057 25058 // these all use the Italian dialling plan 25059 case "IT": // italy 25060 case "SM": // san marino 25061 case "VA": // vatican city 25062 norm = "IT"; 25063 break; 25064 25065 // all the French dependencies are on the French dialling plan 25066 case "FR": // france 25067 case "GF": // french guiana 25068 case "MQ": // martinique 25069 case "GP": // guadeloupe, 25070 case "BL": // saint barthélemy 25071 case "MF": // saint martin 25072 case "RE": // réunion, mayotte 25073 norm = "FR"; 25074 break; 25075 default: 25076 norm = region; 25077 break; 25078 } 25079 return norm; 25080 }; 25081 25082 25083 /*< PhoneHandlerFactory.js */ 25084 /* 25085 * handler.js - Handle phone number parse states 25086 * 25087 * Copyright © 2014-2015, JEDLSoft 25088 * 25089 * Licensed under the Apache License, Version 2.0 (the "License"); 25090 * you may not use this file except in compliance with the License. 25091 * You may obtain a copy of the License at 25092 * 25093 * http://www.apache.org/licenses/LICENSE-2.0 25094 * 25095 * Unless required by applicable law or agreed to in writing, software 25096 * distributed under the License is distributed on an "AS IS" BASIS, 25097 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25098 * 25099 * See the License for the specific language governing permissions and 25100 * limitations under the License. 25101 */ 25102 25103 25104 /** 25105 * @class 25106 * @private 25107 * @constructor 25108 */ 25109 var PhoneHandler = function () { 25110 return this; 25111 }; 25112 25113 PhoneHandler.prototype = { 25114 /** 25115 * @private 25116 * @param {string} number phone number 25117 * @param {Object} fields the fields that have been extracted so far 25118 * @param {Object} regionSettings settings used to parse the rest of the number 25119 */ 25120 processSubscriberNumber: function(number, fields, regionSettings) { 25121 var last; 25122 25123 last = number.search(/[xwtp,;]/i); // last digit of the local number 25124 25125 if (last > -1) { 25126 if (last > 0) { 25127 fields.subscriberNumber = number.substring(0, last); 25128 } 25129 // strip x's which are there to indicate a break between the local subscriber number and the extension, but 25130 // are not themselves a dialable character 25131 fields.extension = number.substring(last).replace('x', ''); 25132 } else { 25133 if (number.length) { 25134 fields.subscriberNumber = number; 25135 } 25136 } 25137 25138 if (regionSettings.plan.getFieldLength('maxLocalLength') && 25139 fields.subscriberNumber && 25140 fields.subscriberNumber.length > regionSettings.plan.getFieldLength('maxLocalLength')) { 25141 fields.invalid = true; 25142 } 25143 }, 25144 /** 25145 * @private 25146 * @param {string} fieldName 25147 * @param {number} length length of phone number 25148 * @param {string} number phone number 25149 * @param {number} currentChar currentChar to be parsed 25150 * @param {Object} fields the fields that have been extracted so far 25151 * @param {Object} regionSettings settings used to parse the rest of the number 25152 * @param {boolean} noExtractTrunk 25153 */ 25154 processFieldWithSubscriberNumber: function(fieldName, length, number, currentChar, fields, regionSettings, noExtractTrunk) { 25155 var ret, end; 25156 25157 if (length !== undefined && length > 0) { 25158 // fixed length 25159 end = length; 25160 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 25161 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 25162 } 25163 } else { 25164 // variable length 25165 // the setting is the negative of the length to add, so subtract to make it positive 25166 end = currentChar + 1 - length; 25167 } 25168 25169 if (fields[fieldName] !== undefined) { 25170 // we have a spurious recognition, because this number already contains that field! So, just put 25171 // everything into the subscriberNumber as the default 25172 this.processSubscriberNumber(number, fields, regionSettings); 25173 } else { 25174 fields[fieldName] = number.substring(0, end); 25175 if (number.length > end) { 25176 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 25177 } 25178 } 25179 25180 ret = { 25181 number: "" 25182 }; 25183 25184 return ret; 25185 }, 25186 /** 25187 * @private 25188 * @param {string} fieldName 25189 * @param {number} length length of phone number 25190 * @param {string} number phone number 25191 * @param {number} currentChar currentChar to be parsed 25192 * @param {Object} fields the fields that have been extracted so far 25193 * @param {Object} regionSettings settings used to parse the rest of the number 25194 */ 25195 processField: function(fieldName, length, number, currentChar, fields, regionSettings) { 25196 var ret = {}, end; 25197 25198 if (length !== undefined && length > 0) { 25199 // fixed length 25200 end = length; 25201 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 25202 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 25203 } 25204 } else { 25205 // variable length 25206 // the setting is the negative of the length to add, so subtract to make it positive 25207 end = currentChar + 1 - length; 25208 } 25209 25210 if (fields[fieldName] !== undefined) { 25211 // we have a spurious recognition, because this number already contains that field! So, just put 25212 // everything into the subscriberNumber as the default 25213 this.processSubscriberNumber(number, fields, regionSettings); 25214 ret.number = ""; 25215 } else { 25216 fields[fieldName] = number.substring(0, end); 25217 ret.number = (number.length > end) ? number.substring(end) : ""; 25218 } 25219 25220 return ret; 25221 }, 25222 /** 25223 * @private 25224 * @param {string} number phone number 25225 * @param {number} currentChar currentChar to be parsed 25226 * @param {Object} fields the fields that have been extracted so far 25227 * @param {Object} regionSettings settings used to parse the rest of the number 25228 */ 25229 trunk: function(number, currentChar, fields, regionSettings) { 25230 var ret, trunkLength; 25231 25232 if (fields.trunkAccess !== undefined) { 25233 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 25234 this.processSubscriberNumber(number, fields, regionSettings); 25235 number = ""; 25236 } else { 25237 trunkLength = regionSettings.plan.getTrunkCode().length; 25238 fields.trunkAccess = number.substring(0, trunkLength); 25239 number = (number.length > trunkLength) ? number.substring(trunkLength) : ""; 25240 } 25241 25242 ret = { 25243 number: number 25244 }; 25245 25246 return ret; 25247 }, 25248 /** 25249 * @private 25250 * @param {string} number phone number 25251 * @param {number} currentChar currentChar to be parsed 25252 * @param {Object} fields the fields that have been extracted so far 25253 * @param {Object} regionSettings settings used to parse the rest of the number 25254 */ 25255 plus: function(number, currentChar, fields, regionSettings) { 25256 var ret = {}; 25257 25258 if (fields.iddPrefix !== undefined) { 25259 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 25260 this.processSubscriberNumber(number, fields, regionSettings); 25261 ret.number = ""; 25262 } else { 25263 // found the idd prefix, so save it and cause the function to parse the next part 25264 // of the number with the idd table 25265 fields.iddPrefix = number.substring(0, 1); 25266 25267 ret = { 25268 number: number.substring(1), 25269 table: 'idd' // shared subtable that parses the country code 25270 }; 25271 } 25272 return ret; 25273 }, 25274 /** 25275 * @private 25276 * @param {string} number phone number 25277 * @param {number} currentChar currentChar to be parsed 25278 * @param {Object} fields the fields that have been extracted so far 25279 * @param {Object} regionSettings settings used to parse the rest of the number 25280 */ 25281 idd: function(number, currentChar, fields, regionSettings) { 25282 var ret = {}; 25283 25284 if (fields.iddPrefix !== undefined) { 25285 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 25286 this.processSubscriberNumber(number, fields, regionSettings); 25287 ret.number = ""; 25288 } else { 25289 // found the idd prefix, so save it and cause the function to parse the next part 25290 // of the number with the idd table 25291 fields.iddPrefix = number.substring(0, currentChar+1); 25292 25293 ret = { 25294 number: number.substring(currentChar+1), 25295 table: 'idd' // shared subtable that parses the country code 25296 }; 25297 } 25298 25299 return ret; 25300 }, 25301 /** 25302 * @private 25303 * @param {string} number phone number 25304 * @param {number} currentChar currentChar to be parsed 25305 * @param {Object} fields the fields that have been extracted so far 25306 * @param {Object} regionSettings settings used to parse the rest of the number 25307 */ 25308 country: function(number, currentChar, fields, regionSettings) { 25309 var ret, cc; 25310 25311 // found the country code of an IDD number, so save it and cause the function to 25312 // parse the rest of the number with the regular table for this locale 25313 fields.countryCode = number.substring(0, currentChar+1); 25314 cc = fields.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 25315 // console.log("Found country code " + fields.countryCode + ". Switching to country " + locale.region + " to parse the rest of the number"); 25316 25317 ret = { 25318 number: number.substring(currentChar+1), 25319 countryCode: cc 25320 }; 25321 25322 return ret; 25323 }, 25324 /** 25325 * @private 25326 * @param {string} number phone number 25327 * @param {number} currentChar currentChar to be parsed 25328 * @param {Object} fields the fields that have been extracted so far 25329 * @param {Object} regionSettings settings used to parse the rest of the number 25330 */ 25331 cic: function(number, currentChar, fields, regionSettings) { 25332 return this.processField('cic', regionSettings.plan.getFieldLength('cic'), number, currentChar, fields, regionSettings); 25333 }, 25334 /** 25335 * @private 25336 * @param {string} number phone number 25337 * @param {number} currentChar currentChar to be parsed 25338 * @param {Object} fields the fields that have been extracted so far 25339 * @param {Object} regionSettings settings used to parse the rest of the number 25340 */ 25341 service: function(number, currentChar, fields, regionSettings) { 25342 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('serviceCode'), number, currentChar, fields, regionSettings, false); 25343 }, 25344 /** 25345 * @private 25346 * @param {string} number phone number 25347 * @param {number} currentChar currentChar to be parsed 25348 * @param {Object} fields the fields that have been extracted so far 25349 * @param {Object} regionSettings settings used to parse the rest of the number 25350 */ 25351 area: function(number, currentChar, fields, regionSettings) { 25352 var ret, last, end, localLength; 25353 25354 last = number.search(/[xwtp]/i); // last digit of the local number 25355 localLength = (last > -1) ? last : number.length; 25356 25357 if (regionSettings.plan.getFieldLength('areaCode') > 0) { 25358 // fixed length 25359 end = regionSettings.plan.getFieldLength('areaCode'); 25360 if (regionSettings.plan.getTrunkCode() === number.charAt(0)) { 25361 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 25362 localLength -= regionSettings.plan.getTrunkCode().length; 25363 } 25364 } else { 25365 // variable length 25366 // the setting is the negative of the length to add, so subtract to make it positive 25367 end = currentChar + 1 - regionSettings.plan.getFieldLength('areaCode'); 25368 } 25369 25370 // substring() extracts the part of the string up to but not including the end character, 25371 // so add one to compensate 25372 if (regionSettings.plan.getFieldLength('maxLocalLength') !== undefined) { 25373 if (fields.trunkAccess !== undefined || fields.mobilePrefix !== undefined || 25374 fields.countryCode !== undefined || 25375 localLength > regionSettings.plan.getFieldLength('maxLocalLength')) { 25376 // too long for a local number by itself, or a different final state already parsed out the trunk 25377 // or mobile prefix, then consider the rest of this number to be an area code + part of the subscriber number 25378 fields.areaCode = number.substring(0, end); 25379 if (number.length > end) { 25380 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 25381 } 25382 } else { 25383 // shorter than the length needed for a local number, so just consider it a local number 25384 this.processSubscriberNumber(number, fields, regionSettings); 25385 } 25386 } else { 25387 fields.areaCode = number.substring(0, end); 25388 if (number.length > end) { 25389 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 25390 } 25391 } 25392 25393 // extensions are separated from the number by a dash in Germany 25394 if (regionSettings.plan.getFindExtensions() !== undefined && fields.subscriberNumber !== undefined) { 25395 var dash = fields.subscriberNumber.indexOf("-"); 25396 if (dash > -1) { 25397 fields.subscriberNumber = fields.subscriberNumber.substring(0, dash); 25398 fields.extension = fields.subscriberNumber.substring(dash+1); 25399 } 25400 } 25401 25402 ret = { 25403 number: "" 25404 }; 25405 25406 return ret; 25407 }, 25408 /** 25409 * @private 25410 * @param {string} number phone number 25411 * @param {number} currentChar currentChar to be parsed 25412 * @param {Object} fields the fields that have been extracted so far 25413 * @param {Object} regionSettings settings used to parse the rest of the number 25414 */ 25415 none: function(number, currentChar, fields, regionSettings) { 25416 var ret; 25417 25418 // this is a last resort function that is called when nothing is recognized. 25419 // When this happens, just put the whole stripped number into the subscriber number 25420 25421 if (number.length > 0) { 25422 this.processSubscriberNumber(number, fields, regionSettings); 25423 if (currentChar > 0 && currentChar < number.length) { 25424 // if we were part-way through parsing, and we hit an invalid digit, 25425 // indicate that the number could not be parsed properly 25426 fields.invalid = true; 25427 } 25428 } 25429 25430 ret = { 25431 number:"" 25432 }; 25433 25434 return ret; 25435 }, 25436 /** 25437 * @private 25438 * @param {string} number phone number 25439 * @param {number} currentChar currentChar to be parsed 25440 * @param {Object} fields the fields that have been extracted so far 25441 * @param {Object} regionSettings settings used to parse the rest of the number 25442 */ 25443 vsc: function(number, currentChar, fields, regionSettings) { 25444 var ret, length, end; 25445 25446 if (fields.vsc === undefined) { 25447 length = regionSettings.plan.getFieldLength('vsc') || 0; 25448 if (length !== undefined && length > 0) { 25449 // fixed length 25450 end = length; 25451 } else { 25452 // variable length 25453 // the setting is the negative of the length to add, so subtract to make it positive 25454 end = currentChar + 1 - length; 25455 } 25456 25457 // found a VSC code (ie. a "star code"), so save it and cause the function to 25458 // parse the rest of the number with the same table for this locale 25459 fields.vsc = number.substring(0, end); 25460 number = (number.length > end) ? "^" + number.substring(end) : ""; 25461 } else { 25462 // got it twice??? Okay, this is a bogus number then. Just put everything else into the subscriber number as the default 25463 this.processSubscriberNumber(number, fields, regionSettings); 25464 number = ""; 25465 } 25466 25467 // treat the rest of the number as if it were a completely new number 25468 ret = { 25469 number: number 25470 }; 25471 25472 return ret; 25473 }, 25474 /** 25475 * @private 25476 * @param {string} number phone number 25477 * @param {number} currentChar currentChar to be parsed 25478 * @param {Object} fields the fields that have been extracted so far 25479 * @param {Object} regionSettings settings used to parse the rest of the number 25480 */ 25481 cell: function(number, currentChar, fields, regionSettings) { 25482 return this.processFieldWithSubscriberNumber('mobilePrefix', regionSettings.plan.getFieldLength('mobilePrefix'), number, currentChar, fields, regionSettings, false); 25483 }, 25484 /** 25485 * @private 25486 * @param {string} number phone number 25487 * @param {number} currentChar currentChar to be parsed 25488 * @param {Object} fields the fields that have been extracted so far 25489 * @param {Object} regionSettings settings used to parse the rest of the number 25490 */ 25491 personal: function(number, currentChar, fields, regionSettings) { 25492 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('personal'), number, currentChar, fields, regionSettings, false); 25493 }, 25494 /** 25495 * @private 25496 * @param {string} number phone number 25497 * @param {number} currentChar currentChar to be parsed 25498 * @param {Object} fields the fields that have been extracted so far 25499 * @param {Object} regionSettings settings used to parse the rest of the number 25500 */ 25501 emergency: function(number, currentChar, fields, regionSettings) { 25502 return this.processFieldWithSubscriberNumber('emergency', regionSettings.plan.getFieldLength('emergency'), number, currentChar, fields, regionSettings, true); 25503 }, 25504 /** 25505 * @private 25506 * @param {string} number phone number 25507 * @param {number} currentChar currentChar to be parsed 25508 * @param {Object} fields the fields that have been extracted so far 25509 * @param {Object} regionSettings settings used to parse the rest of the number 25510 */ 25511 premium: function(number, currentChar, fields, regionSettings) { 25512 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('premium'), number, currentChar, fields, regionSettings, false); 25513 }, 25514 /** 25515 * @private 25516 * @param {string} number phone number 25517 * @param {number} currentChar currentChar to be parsed 25518 * @param {Object} fields the fields that have been extracted so far 25519 * @param {Object} regionSettings settings used to parse the rest of the number 25520 */ 25521 special: function(number, currentChar, fields, regionSettings) { 25522 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('special'), number, currentChar, fields, regionSettings, false); 25523 }, 25524 /** 25525 * @private 25526 * @param {string} number phone number 25527 * @param {number} currentChar currentChar to be parsed 25528 * @param {Object} fields the fields that have been extracted so far 25529 * @param {Object} regionSettings settings used to parse the rest of the number 25530 */ 25531 service2: function(number, currentChar, fields, regionSettings) { 25532 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service2'), number, currentChar, fields, regionSettings, false); 25533 }, 25534 /** 25535 * @private 25536 * @param {string} number phone number 25537 * @param {number} currentChar currentChar to be parsed 25538 * @param {Object} fields the fields that have been extracted so far 25539 * @param {Object} regionSettings settings used to parse the rest of the number 25540 */ 25541 service3: function(number, currentChar, fields, regionSettings) { 25542 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service3'), number, currentChar, fields, regionSettings, false); 25543 }, 25544 /** 25545 * @private 25546 * @param {string} number phone number 25547 * @param {number} currentChar currentChar to be parsed 25548 * @param {Object} fields the fields that have been extracted so far 25549 * @param {Object} regionSettings settings used to parse the rest of the number 25550 */ 25551 service4: function(number, currentChar, fields, regionSettings) { 25552 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service4'), number, currentChar, fields, regionSettings, false); 25553 }, 25554 /** 25555 * @private 25556 * @param {string} number phone number 25557 * @param {number} currentChar currentChar to be parsed 25558 * @param {Object} fields the fields that have been extracted so far 25559 * @param {Object} regionSettings settings used to parse the rest of the number 25560 */ 25561 cic2: function(number, currentChar, fields, regionSettings) { 25562 return this.processField('cic', regionSettings.plan.getFieldLength('cic2'), number, currentChar, fields, regionSettings); 25563 }, 25564 /** 25565 * @private 25566 * @param {string} number phone number 25567 * @param {number} currentChar currentChar to be parsed 25568 * @param {Object} fields the fields that have been extracted so far 25569 * @param {Object} regionSettings settings used to parse the rest of the number 25570 */ 25571 cic3: function(number, currentChar, fields, regionSettings) { 25572 return this.processField('cic', regionSettings.plan.getFieldLength('cic3'), number, currentChar, fields, regionSettings); 25573 }, 25574 /** 25575 * @private 25576 * @param {string} number phone number 25577 * @param {number} currentChar currentChar to be parsed 25578 * @param {Object} fields the fields that have been extracted so far 25579 * @param {Object} regionSettings settings used to parse the rest of the number 25580 */ 25581 start: function(number, currentChar, fields, regionSettings) { 25582 // don't do anything except transition to the next state 25583 return { 25584 number: number 25585 }; 25586 }, 25587 /** 25588 * @private 25589 * @param {string} number phone number 25590 * @param {number} currentChar currentChar to be parsed 25591 * @param {Object} fields the fields that have been extracted so far 25592 * @param {Object} regionSettings settings used to parse the rest of the number 25593 */ 25594 local: function(number, currentChar, fields, regionSettings) { 25595 // in open dialling plans, we can tell that this number is a local subscriber number because it 25596 // starts with a digit that indicates as such 25597 this.processSubscriberNumber(number, fields, regionSettings); 25598 return { 25599 number: "" 25600 }; 25601 } 25602 }; 25603 25604 // context-sensitive handler 25605 /** 25606 * @class 25607 * @private 25608 * @extends PhoneHandler 25609 * @constructor 25610 */ 25611 var CSStateHandler = function () { 25612 return this; 25613 }; 25614 25615 CSStateHandler.prototype = new PhoneHandler(); 25616 CSStateHandler.prototype.special = function (number, currentChar, fields, regionSettings) { 25617 var ret; 25618 25619 // found a special area code that is both a node and a leaf. In 25620 // this state, we have found the leaf, so chop off the end 25621 // character to make it a leaf. 25622 if (number.charAt(0) === "0") { 25623 fields.trunkAccess = number.charAt(0); 25624 fields.areaCode = number.substring(1, currentChar); 25625 } else { 25626 fields.areaCode = number.substring(0, currentChar); 25627 } 25628 this.processSubscriberNumber(number.substring(currentChar), fields, regionSettings); 25629 25630 ret = { 25631 number: "" 25632 }; 25633 25634 return ret; 25635 }; 25636 25637 /** 25638 * @class 25639 * @private 25640 * @extends PhoneHandler 25641 * @constructor 25642 */ 25643 var USStateHandler = function () { 25644 return this; 25645 }; 25646 25647 USStateHandler.prototype = new PhoneHandler(); 25648 USStateHandler.prototype.vsc = function (number, currentChar, fields, regionSettings) { 25649 var ret; 25650 25651 // found a VSC code (ie. a "star code") 25652 fields.vsc = number; 25653 25654 // treat the rest of the number as if it were a completely new number 25655 ret = { 25656 number: "" 25657 }; 25658 25659 return ret; 25660 }; 25661 25662 /** 25663 * Creates a phone handler instance that is correct for the locale. Phone handlers are 25664 * used to handle parsing of the various fields in a phone number. 25665 * 25666 * @protected 25667 * @static 25668 * @return {PhoneHandler} the correct phone handler for the locale 25669 */ 25670 var PhoneHandlerFactory = function (locale, plan) { 25671 if (plan.getContextFree() !== undefined && typeof(plan.getContextFree()) === 'boolean' && plan.getContextFree() === false) { 25672 return new CSStateHandler(); 25673 } 25674 var region = (locale && locale.getRegion()) || "ZZ"; 25675 switch (region) { 25676 case 'US': 25677 return new USStateHandler(); 25678 break; 25679 default: 25680 return new PhoneHandler(); 25681 } 25682 }; 25683 25684 25685 /*< PhoneNumber.js */ 25686 /* 25687 * phonenum.js - Represent a phone number. 25688 * 25689 * Copyright © 2014-2015, JEDLSoft 25690 * 25691 * Licensed under the Apache License, Version 2.0 (the "License"); 25692 * you may not use this file except in compliance with the License. 25693 * You may obtain a copy of the License at 25694 * 25695 * http://www.apache.org/licenses/LICENSE-2.0 25696 * 25697 * Unless required by applicable law or agreed to in writing, software 25698 * distributed under the License is distributed on an "AS IS" BASIS, 25699 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25700 * 25701 * See the License for the specific language governing permissions and 25702 * limitations under the License. 25703 */ 25704 25705 /* 25706 !depends 25707 ilib.js 25708 NumberingPlan.js 25709 PhoneLocale.js 25710 PhoneHandlerFactory.js 25711 Utils.js 25712 JSUtils.js 25713 */ 25714 25715 // !data states idd mnc 25716 25717 25718 /** 25719 * @class 25720 * Create a new phone number instance that parses the phone number parameter for its 25721 * constituent parts, and store them as separate fields in the returned object. 25722 * 25723 * The options object may include any of these properties: 25724 * 25725 * <ul> 25726 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 25727 * numbering plan to use. 25728 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 25729 * currently connected to, if known. This also can give a clue as to which numbering plan to 25730 * use 25731 * <li>onLoad - a callback function to call when this instance is fully 25732 * loaded. When the onLoad option is given, this class will attempt to 25733 * load any missing locale data using the ilib loader callback. 25734 * When the constructor is done (even if the data is already preassembled), the 25735 * onLoad function is called with the current instance as a parameter, so this 25736 * callback can be used with preassembled or dynamic loading or a mix of the two. 25737 * <li>sync - tell whether to load any missing locale data synchronously or 25738 * asynchronously. If this option is given as "false", then the "onLoad" 25739 * callback must be given, as the instance returned from this constructor will 25740 * not be usable for a while. 25741 * <li><i>loadParams</i> - an object containing parameters to pass to the 25742 * loader callback function when locale data is missing. The parameters are not 25743 * interpretted or modified in any way. They are simply passed along. The object 25744 * may contain any property/value pairs as long as the calling code is in 25745 * agreement with the loader callback function as to what those parameters mean. 25746 * </ul> 25747 * 25748 * This function is locale-sensitive, and will assume any number passed to it is 25749 * appropriate for the given locale. If the MCC is given, this method will assume 25750 * that numbers without an explicit country code have been dialled within the country 25751 * given by the MCC. This affects how things like area codes are parsed. If the MCC 25752 * is not given, this method will use the given locale to determine the country 25753 * code. If the locale is not explicitly given either, then this function uses the 25754 * region of current locale as the default.<p> 25755 * 25756 * The input number may contain any formatting characters for the given locale. Each 25757 * field that is returned in the json object is a simple string of digits with 25758 * all formatting and whitespace characters removed.<p> 25759 * 25760 * The number is decomposed into its parts, regardless if the number 25761 * contains formatting characters. If a particular part cannot be extracted from given 25762 * number, the field will not be returned as a field in the object. If no fields can be 25763 * extracted from the number at all, then all digits found in the string will be 25764 * returned in the subscriberNumber field. If the number parameter contains no 25765 * digits, an empty object is returned.<p> 25766 * 25767 * This instance can contain any of the following fields after parsing is done: 25768 * 25769 * <ul> 25770 * <li>vsc - if this number starts with a VSC (Vertical Service Code, or "star code"), this field will contain the star and the code together 25771 * <li>iddPrefix - the prefix for international direct dialing. This can either be in the form of a plus character or the IDD access code for the given locale 25772 * <li>countryCode - if this number is an international direct dial number, this is the country code 25773 * <li>cic - for "dial-around" services (access to other carriers), this is the prefix used as the carrier identification code 25774 * <li>emergency - an emergency services number 25775 * <li>mobilePrefix - prefix that introduces a mobile phone number 25776 * <li>trunkAccess - trunk access code (long-distance access) 25777 * <li>serviceCode - like a geographic area code, but it is a required prefix for various services 25778 * <li>areaCode - geographic area codes 25779 * <li>subscriberNumber - the unique number of the person or company that pays for this phone line 25780 * <li>extension - in some countries, extensions are dialed directly without going through an operator or a voice prompt system. If the number includes an extension, it is given in this field. 25781 * <li>invalid - this property is added and set to true if the parser found that the number is invalid in the numbering plan for the country. This method will make its best effort at parsing, but any digits after the error will go into the subscriberNumber field 25782 * </ul> 25783 * 25784 * The following rules determine how the number is parsed: 25785 * 25786 * <ol> 25787 * <li>If the number starts with a character that is alphabetic instead of numeric, do 25788 * not parse the number at all. There is a good chance that it is not really a phone number. 25789 * In this case, an empty instance will be returned. 25790 * <li>If the phone number uses the plus notation or explicitly uses the international direct 25791 * dialing prefix for the given locale, then the country code is identified in 25792 * the number. The rules of given locale are used to parse the IDD prefix, and then the rules 25793 * of the country in the prefix are used to parse the rest of the number. 25794 * <li>If a country code is provided as an argument to the function call, use that country's 25795 * parsing rules for the number. This is intended for programs like a Contacts application that 25796 * know what the country is of the person that owns the phone number and can pass that on as 25797 * a hint. 25798 * <li>If the appropriate locale cannot be easily determined, default to using the rules 25799 * for the current user's region. 25800 * </ol> 25801 * 25802 * Example: parsing the number "+49 02101345345-78" will give the following properties in the 25803 * resulting phone number instance: 25804 * 25805 * <pre> 25806 * { 25807 * iddPrefix: "+", 25808 * countryCode: "49", 25809 * areaCode: "02101", 25810 * subscriberNumber: "345345", 25811 * extension: "78" 25812 * } 25813 * </pre> 25814 * 25815 * Note that in this example, because international direct dialing is explicitly used 25816 * in the number, the part of this number after the IDD prefix and country code will be 25817 * parsed exactly the same way in all locales with German rules (country code 49). 25818 * 25819 * Regions currently supported are: 25820 * 25821 * <ul> 25822 * <li>NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations 25823 * <li>UK 25824 * <li>Republic of Ireland 25825 * <li>Germany 25826 * <li>France 25827 * <li>Spain 25828 * <li>Italy 25829 * <li>Mexico 25830 * <li>India 25831 * <li>People's Republic of China 25832 * <li>Netherlands 25833 * <li>Belgium 25834 * <li>Luxembourg 25835 * <li>Australia 25836 * <li>New Zealand 25837 * <li>Singapore 25838 * <li>Korea 25839 * <li>Japan 25840 * <li>Russia 25841 * <li>Brazil 25842 * </ul> 25843 * 25844 * @constructor 25845 * @param {!string|PhoneNumber} number A free-form phone number to be parsed, or another phone 25846 * number instance to copy 25847 * @param {Object=} options options that guide the parser in parsing the number 25848 */ 25849 var PhoneNumber = function(number, options) { 25850 var stateData, 25851 regionSettings; 25852 25853 this.sync = true; 25854 this.loadParams = {}; 25855 25856 if (!number || (typeof number === "string" && number.length === 0)) { 25857 return this; 25858 } 25859 25860 if (options) { 25861 if (typeof(options.sync) === 'boolean') { 25862 this.sync = options.sync; 25863 } 25864 25865 if (options.loadParams) { 25866 this.loadParams = options.loadParams; 25867 } 25868 25869 if (typeof(options.onLoad) === 'function') { 25870 /** 25871 * @private 25872 * @type {function(PhoneNumber)} 25873 */ 25874 this.onLoad = options.onLoad; 25875 } 25876 } 25877 25878 if (typeof number === "object") { 25879 /** 25880 * The vertical service code. These are codes that typically 25881 * start with a star or hash, like "*69" for "dial back the 25882 * last number that called me". 25883 * @type {string|undefined} 25884 */ 25885 this.vsc = number.vsc; 25886 25887 /** 25888 * The international direct dialing prefix. This is always 25889 * followed by the country code. 25890 * @type {string} 25891 */ 25892 this.iddPrefix = number.iddPrefix; 25893 25894 /** 25895 * The unique IDD country code for the country where the 25896 * phone number is serviced. 25897 * @type {string|undefined} 25898 */ 25899 this.countryCode = number.countryCode; 25900 25901 /** 25902 * The digits required to access the trunk. 25903 * @type {string|undefined} 25904 */ 25905 this.trunkAccess = number.trunkAccess; 25906 25907 /** 25908 * The carrier identification code used to identify 25909 * alternate long distance or international carriers. 25910 * @type {string|undefined} 25911 */ 25912 this.cic = number.cic; 25913 25914 /** 25915 * Identifies an emergency number that is typically 25916 * short, such as "911" in North America or "112" in 25917 * many other places in the world. 25918 * @type {string|undefined} 25919 */ 25920 this.emergency = number.emergency; 25921 25922 /** 25923 * The prefix of the subscriber number that indicates 25924 * that this is the number of a mobile phone. 25925 * @type {string|undefined} 25926 */ 25927 this.mobilePrefix = number.mobilePrefix; 25928 25929 /** 25930 * The prefix that identifies this number as commercial 25931 * service number. 25932 * @type {string|undefined} 25933 */ 25934 this.serviceCode = number.serviceCode; 25935 25936 /** 25937 * The area code prefix of a land line number. 25938 * @type {string|undefined} 25939 */ 25940 this.areaCode = number.areaCode; 25941 25942 /** 25943 * The unique number associated with the subscriber 25944 * of this phone. 25945 * @type {string|undefined} 25946 */ 25947 this.subscriberNumber = number.subscriberNumber; 25948 25949 /** 25950 * The direct dial extension number. 25951 * @type {string|undefined} 25952 */ 25953 this.extension = number.extension; 25954 25955 /** 25956 * @private 25957 * @type {boolean} 25958 */ 25959 this.invalid = number.invalid; 25960 25961 if (number.plan && number.locale) { 25962 /** 25963 * @private 25964 * @type {NumberingPlan} 25965 */ 25966 this.plan = number.plan; 25967 25968 /** 25969 * @private 25970 * @type {PhoneLocale} 25971 */ 25972 this.locale = number.locale; 25973 25974 /** 25975 * @private 25976 * @type {NumberingPlan} 25977 */ 25978 this.destinationPlan = number.destinationPlan; 25979 25980 /** 25981 * @private 25982 * @type {PhoneLocale} 25983 */ 25984 this.destinationLocale = number.destinationLocale; 25985 25986 if (options && typeof(options.onLoad) === 'function') { 25987 options.onLoad(this); 25988 } 25989 return; 25990 } 25991 } 25992 25993 new PhoneLocale({ 25994 locale: options && options.locale, 25995 mcc: options && options.mcc, 25996 sync: this.sync, 25997 loadParams: this.loadParams, 25998 onLoad: ilib.bind(this, function(loc) { 25999 this.locale = this.destinationLocale = loc; 26000 new NumberingPlan({ 26001 locale: this.locale, 26002 sync: this.sync, 26003 loadParms: this.loadParams, 26004 onLoad: ilib.bind(this, function (plan) { 26005 this.plan = this.destinationPlan = plan; 26006 26007 if (typeof number === "object") { 26008 // the copy constructor code above did not find the locale 26009 // or plan before, but now they are loaded, so we can return 26010 // already without going further 26011 return; 26012 } 26013 Utils.loadData({ 26014 name: "states.json", 26015 object: PhoneNumber, 26016 locale: this.locale, 26017 sync: this.sync, 26018 loadParams: JSUtils.merge(this.loadParams, { 26019 returnOne: true 26020 }), 26021 callback: ilib.bind(this, function (stdata) { 26022 if (!stdata) { 26023 stdata = PhoneNumber._defaultStates; 26024 } 26025 26026 stateData = stdata; 26027 26028 regionSettings = { 26029 stateData: stateData, 26030 plan: plan, 26031 handler: PhoneHandlerFactory(this.locale, plan) 26032 }; 26033 26034 // use ^ to indicate the beginning of the number, because certain things only match at the beginning 26035 number = "^" + number.replace(/\^/g, ''); 26036 number = PhoneNumber._stripFormatting(number); 26037 26038 this._parseNumber(number, regionSettings, options); 26039 }) 26040 }); 26041 }) 26042 }); 26043 }) 26044 }); 26045 }; 26046 26047 /** 26048 * Parse an International Mobile Subscriber Identity (IMSI) number into its 3 constituent parts: 26049 * 26050 * <ol> 26051 * <li>mcc - Mobile Country Code, which identifies the country where the phone is currently receiving 26052 * service. 26053 * <li>mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone 26054 * <li>msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on 26055 * the network, which usually maps to an account/subscriber in the carrier's database. 26056 * </ol> 26057 * 26058 * Because this function may need to load data to identify the above parts, you can pass an options 26059 * object that controls how the data is loaded. The options may contain any of the following properties: 26060 * 26061 * <ul> 26062 * <li>onLoad - a callback function to call when the parsing is done. When the onLoad option is given, 26063 * this method will attempt to load the locale data using the ilib loader callback. When it is done 26064 * (even if the data is already preassembled), the onLoad function is called with the parsing results 26065 * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or 26066 * asynchronous loading or a mix of the above. 26067 * <li>sync - tell whether to load any missing locale data synchronously or asynchronously. If this 26068 * option is given as "false", then the "onLoad" callback must be given, as the results returned from 26069 * this constructor will not be usable for a while. 26070 * <li><i>loadParams</i> - an object containing parameters to pass to the loader callback function 26071 * when locale data is missing. The parameters are not interpretted or modified in any way. They are 26072 * simply passed along. The object may contain any property/value pairs as long as the calling code is in 26073 * agreement with the loader callback function as to what those parameters mean. 26074 * </ul> 26075 * 26076 * @static 26077 * @param {string} imsi IMSI number to parse 26078 * @param {Object} options options controlling the loading of the locale data 26079 * @return {{mcc:string,mnc:string,msin:string}|undefined} components of the IMSI number, when the locale data 26080 * is loaded synchronously, or undefined if asynchronous 26081 */ 26082 PhoneNumber.parseImsi = function(imsi, options) { 26083 var sync = true, 26084 loadParams = {}, 26085 fields = {}; 26086 26087 if (!imsi) { 26088 return undefined; 26089 } 26090 26091 if (options) { 26092 if (typeof(options.sync) !== 'undefined') { 26093 sync = (options.sync == true); 26094 } 26095 26096 if (options.loadParams) { 26097 loadParams = options.loadParams; 26098 } 26099 } 26100 26101 if (ilib.data.mnc) { 26102 fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi); 26103 26104 if (options && typeof(options.onLoad) === 'function') { 26105 options.onLoad(fields); 26106 } 26107 } else { 26108 Utils.loadData({ 26109 name: "mnc.json", 26110 object: PhoneNumber, 26111 nonlocale: true, 26112 sync: sync, 26113 loadParams: loadParams, 26114 callback: ilib.bind(this, function(data) { 26115 ilib.data.mnc = data; 26116 fields = PhoneNumber._parseImsi(data, imsi); 26117 26118 if (options && typeof(options.onLoad) === 'function') { 26119 options.onLoad(fields); 26120 } 26121 }) 26122 }); 26123 } 26124 return fields; 26125 }; 26126 26127 26128 /** 26129 * @static 26130 * @protected 26131 */ 26132 PhoneNumber._parseImsi = function(data, imsi) { 26133 var ch, 26134 i, 26135 currentState, 26136 end, 26137 handlerMethod, 26138 state = 0, 26139 newState, 26140 fields = {}, 26141 lastLeaf, 26142 consumed = 0; 26143 26144 currentState = data; 26145 if (!currentState) { 26146 // can't parse anything 26147 return undefined; 26148 } 26149 26150 i = 0; 26151 while (i < imsi.length) { 26152 ch = PhoneNumber._getCharacterCode(imsi.charAt(i)); 26153 // console.info("parsing char " + imsi.charAt(i) + " code: " + ch); 26154 if (ch >= 0) { 26155 newState = currentState.s && currentState.s[ch]; 26156 26157 if (typeof(newState) === 'object') { 26158 if (typeof(newState.l) !== 'undefined') { 26159 // save for latter if needed 26160 lastLeaf = newState; 26161 consumed = i; 26162 } 26163 // console.info("recognized digit " + ch + " continuing..."); 26164 // recognized digit, so continue parsing 26165 currentState = newState; 26166 i++; 26167 } else { 26168 if ((typeof(newState) === 'undefined' || newState === 0 || 26169 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 26170 lastLeaf) { 26171 // this is possibly a look-ahead and it didn't work... 26172 // so fall back to the last leaf and use that as the 26173 // final state 26174 newState = lastLeaf; 26175 i = consumed; 26176 } 26177 26178 if ((typeof(newState) === 'number' && newState) || 26179 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 26180 // final state 26181 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 26182 handlerMethod = PhoneNumber._states[stateNumber]; 26183 26184 // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 26185 26186 // deal with syntactic ambiguity by using the "special" end state instead of "area" 26187 if ( handlerMethod === "area" ) { 26188 end = i+1; 26189 } else { 26190 // unrecognized imsi, so just assume the mnc is 3 digits 26191 end = 6; 26192 } 26193 26194 fields.mcc = imsi.substring(0,3); 26195 fields.mnc = imsi.substring(3,end); 26196 fields.msin = imsi.substring(end); 26197 26198 return fields; 26199 } else { 26200 // parse error 26201 if (imsi.length >= 6) { 26202 fields.mcc = imsi.substring(0,3); 26203 fields.mnc = imsi.substring(3,6); 26204 fields.msin = imsi.substring(6); 26205 } 26206 return fields; 26207 } 26208 } 26209 } else if (ch === -1) { 26210 // non-transition character, continue parsing in the same state 26211 i++; 26212 } else { 26213 // should not happen 26214 // console.info("skipping character " + ch); 26215 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 26216 i++; 26217 } 26218 } 26219 26220 if (i >= imsi.length && imsi.length >= 6) { 26221 // we reached the end of the imsi, but did not finish recognizing anything. 26222 // Default to last resort and assume 3 digit mnc 26223 fields.mcc = imsi.substring(0,3); 26224 fields.mnc = imsi.substring(3,6); 26225 fields.msin = imsi.substring(6); 26226 } else { 26227 // unknown or not enough characters for a real imsi 26228 fields = undefined; 26229 } 26230 26231 // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields)); 26232 return fields; 26233 }; 26234 26235 /** 26236 * @static 26237 * @private 26238 */ 26239 PhoneNumber._stripFormatting = function(str) { 26240 var ret = ""; 26241 var i; 26242 26243 for (i = 0; i < str.length; i++) { 26244 if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) { 26245 ret += str.charAt(i); 26246 } 26247 } 26248 return ret; 26249 }; 26250 26251 /** 26252 * @static 26253 * @protected 26254 */ 26255 PhoneNumber._getCharacterCode = function(ch) { 26256 if (ch >= '0' && ch <= '9') { 26257 return ch - '0'; 26258 } 26259 switch (ch) { 26260 case '+': 26261 return 10; 26262 case '*': 26263 return 11; 26264 case '#': 26265 return 12; 26266 case '^': 26267 return 13; 26268 case 'p': // pause chars 26269 case 'P': 26270 case 't': 26271 case 'T': 26272 case 'w': 26273 case 'W': 26274 return -1; 26275 case 'x': 26276 case 'X': 26277 case ',': 26278 case ';': // extension char 26279 return -1; 26280 } 26281 return -2; 26282 }; 26283 26284 /** 26285 * @private 26286 */ 26287 PhoneNumber._states = [ 26288 "none", 26289 "unknown", 26290 "plus", 26291 "idd", 26292 "cic", 26293 "service", 26294 "cell", 26295 "area", 26296 "vsc", 26297 "country", 26298 "personal", 26299 "special", 26300 "trunk", 26301 "premium", 26302 "emergency", 26303 "service2", 26304 "service3", 26305 "service4", 26306 "cic2", 26307 "cic3", 26308 "start", 26309 "local" 26310 ]; 26311 26312 /** 26313 * @private 26314 */ 26315 PhoneNumber._fieldOrder = [ 26316 "vsc", 26317 "iddPrefix", 26318 "countryCode", 26319 "trunkAccess", 26320 "cic", 26321 "emergency", 26322 "mobilePrefix", 26323 "serviceCode", 26324 "areaCode", 26325 "subscriberNumber", 26326 "extension" 26327 ]; 26328 26329 PhoneNumber._defaultStates = { 26330 "s": [ 26331 0, 26332 21, // 1 -> local 26333 21, // 2 -> local 26334 21, // 3 -> local 26335 21, // 4 -> local 26336 21, // 5 -> local 26337 21, // 6 -> local 26338 21, // 7 -> local 26339 21, // 8 -> local 26340 21, // 9 -> local 26341 0,0,0, 26342 { // ^ 26343 "s": [ 26344 { // ^0 26345 "s": [3], // ^00 -> idd 26346 "l": 12 // ^0 -> trunk 26347 }, 26348 21, // ^1 -> local 26349 21, // ^2 -> local 26350 21, // ^3 -> local 26351 21, // ^4 -> local 26352 21, // ^5 -> local 26353 21, // ^6 -> local 26354 21, // ^7 -> local 26355 21, // ^8 -> local 26356 21, // ^9 -> local 26357 2 // ^+ -> plus 26358 ] 26359 } 26360 ] 26361 }; 26362 26363 PhoneNumber.prototype = { 26364 /** 26365 * @protected 26366 * @param {string} number 26367 * @param {Object} regionData 26368 * @param {Object} options 26369 * @param {string} countryCode 26370 */ 26371 _parseOtherCountry: function(number, regionData, options, countryCode) { 26372 new PhoneLocale({ 26373 locale: this.locale, 26374 countryCode: countryCode, 26375 sync: this.sync, 26376 loadParms: this.loadParams, 26377 onLoad: ilib.bind(this, function (loc) { 26378 /* 26379 * this.locale is the locale where this number is being parsed, 26380 * and is used to parse the IDD prefix, if any, and this.destinationLocale is 26381 * the locale of the rest of this number after the IDD prefix. 26382 */ 26383 /** @type {PhoneLocale} */ 26384 this.destinationLocale = loc; 26385 26386 Utils.loadData({ 26387 name: "states.json", 26388 object: PhoneNumber, 26389 locale: this.destinationLocale, 26390 sync: this.sync, 26391 loadParams: JSUtils.merge(this.loadParams, { 26392 returnOne: true 26393 }), 26394 callback: ilib.bind(this, function (stateData) { 26395 if (!stateData) { 26396 stateData = PhoneNumber._defaultStates; 26397 } 26398 26399 new NumberingPlan({ 26400 locale: this.destinationLocale, 26401 sync: this.sync, 26402 loadParms: this.loadParams, 26403 onLoad: ilib.bind(this, function (plan) { 26404 /* 26405 * this.plan is the plan where this number is being parsed, 26406 * and is used to parse the IDD prefix, if any, and this.destinationPlan is 26407 * the plan of the rest of this number after the IDD prefix in the 26408 * destination locale. 26409 */ 26410 /** @type {NumberingPlan} */ 26411 this.destinationPlan = plan; 26412 26413 var regionSettings = { 26414 stateData: stateData, 26415 plan: plan, 26416 handler: PhoneHandlerFactory(this.destinationLocale, plan) 26417 }; 26418 26419 // for plans that do not skip the trunk code when dialing from 26420 // abroad, we need to treat the number from here on in as if it 26421 // were parsing a local number from scratch. That way, the parser 26422 // does not get confused between parts of the number at the 26423 // beginning of the number, and parts in the middle. 26424 if (!plan.getSkipTrunk()) { 26425 number = '^' + number; 26426 } 26427 26428 // recursively call the parser with the new states data 26429 // to finish the parsing 26430 this._parseNumber(number, regionSettings, options); 26431 }) 26432 }); 26433 }) 26434 }); 26435 }) 26436 }); 26437 }, 26438 26439 /** 26440 * @protected 26441 * @param {string} number 26442 * @param {Object} regionData 26443 * @param {Object} options 26444 */ 26445 _parseNumber: function(number, regionData, options) { 26446 var i, ch, 26447 regionSettings, 26448 newState, 26449 dot, 26450 handlerMethod, 26451 result, 26452 currentState = regionData.stateData, 26453 lastLeaf = undefined, 26454 consumed = 0; 26455 26456 regionSettings = regionData; 26457 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 26458 26459 i = 0; 26460 while (i < number.length) { 26461 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 26462 if (ch >= 0) { 26463 // newState = stateData.states[state][ch]; 26464 newState = currentState.s && currentState.s[ch]; 26465 26466 if (!newState && currentState.s && currentState.s[dot]) { 26467 newState = currentState.s[dot]; 26468 } 26469 26470 if (typeof(newState) === 'object' && i+1 < number.length) { 26471 if (typeof(newState.l) !== 'undefined') { 26472 // this is a leaf node, so save that for later if needed 26473 lastLeaf = newState; 26474 consumed = i; 26475 } 26476 // console.info("recognized digit " + ch + " continuing..."); 26477 // recognized digit, so continue parsing 26478 currentState = newState; 26479 i++; 26480 } else { 26481 if ((typeof(newState) === 'undefined' || newState === 0 || 26482 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 26483 lastLeaf) { 26484 // this is possibly a look-ahead and it didn't work... 26485 // so fall back to the last leaf and use that as the 26486 // final state 26487 newState = lastLeaf; 26488 i = consumed; 26489 consumed = 0; 26490 lastLeaf = undefined; 26491 } 26492 26493 if ((typeof(newState) === 'number' && newState) || 26494 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 26495 // final state 26496 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 26497 handlerMethod = PhoneNumber._states[stateNumber]; 26498 26499 if (number.charAt(0) === '^') { 26500 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26501 } else { 26502 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26503 } 26504 26505 // reparse whatever is left 26506 number = result.number; 26507 i = consumed = 0; 26508 lastLeaf = undefined; 26509 26510 //console.log("reparsing with new number: " + number); 26511 currentState = regionSettings.stateData; 26512 // if the handler requested a special sub-table, use it for this round of parsing, 26513 // otherwise, set it back to the regular table to continue parsing 26514 26515 if (result.countryCode !== undefined) { 26516 this._parseOtherCountry(number, regionData, options, result.countryCode); 26517 // don't process any further -- let the work be done in the onLoad callbacks 26518 return; 26519 } else if (result.table !== undefined) { 26520 Utils.loadData({ 26521 name: result.table + ".json", 26522 object: PhoneNumber, 26523 nonlocale: true, 26524 sync: this.sync, 26525 loadParams: this.loadParams, 26526 callback: ilib.bind(this, function (data) { 26527 if (!data) { 26528 data = PhoneNumber._defaultStates; 26529 } 26530 26531 regionSettings = { 26532 stateData: data, 26533 plan: regionSettings.plan, 26534 handler: regionSettings.handler 26535 }; 26536 26537 // recursively call the parser with the new states data 26538 // to finish the parsing 26539 this._parseNumber(number, regionSettings, options); 26540 }) 26541 }); 26542 // don't process any further -- let the work be done in the onLoad callbacks 26543 return; 26544 } else if (result.skipTrunk !== undefined) { 26545 ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode()); 26546 currentState = currentState.s && currentState.s[ch]; 26547 } 26548 } else { 26549 handlerMethod = (typeof(newState) === 'number') ? "none" : "local"; 26550 // failed parse. Either no last leaf to fall back to, or there was an explicit 26551 // zero in the table. Put everything else in the subscriberNumber field as the 26552 // default place 26553 if (number.charAt(0) === '^') { 26554 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26555 } else { 26556 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26557 } 26558 break; 26559 } 26560 } 26561 } else if (ch === -1) { 26562 // non-transition character, continue parsing in the same state 26563 i++; 26564 } else { 26565 // should not happen 26566 // console.info("skipping character " + ch); 26567 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 26568 i++; 26569 } 26570 } 26571 if (i >= number.length && currentState !== regionData.stateData) { 26572 handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local"; 26573 // we reached the end of the phone number, but did not finish recognizing anything. 26574 // Default to last resort and put everything that is left into the subscriber number 26575 //console.log("Reached end of number before parsing was complete. Using handler for method none.") 26576 if (number.charAt(0) === '^') { 26577 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26578 } else { 26579 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26580 } 26581 } 26582 26583 // let the caller know we are done parsing 26584 if (this.onLoad) { 26585 this.onLoad(this); 26586 } 26587 }, 26588 /** 26589 * @protected 26590 */ 26591 _getPrefix: function() { 26592 return this.areaCode || this.serviceCode || this.mobilePrefix || ""; 26593 }, 26594 26595 /** 26596 * @protected 26597 */ 26598 _hasPrefix: function() { 26599 return (this._getPrefix() !== ""); 26600 }, 26601 26602 /** 26603 * Exclusive or -- return true, if one is defined and the other isn't 26604 * @protected 26605 */ 26606 _xor : function(left, right) { 26607 if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) { 26608 return false; 26609 } else { 26610 return true; 26611 } 26612 }, 26613 26614 /** 26615 * return a version of the phone number that contains only the dialable digits in the correct order 26616 * @protected 26617 */ 26618 _join: function () { 26619 var fieldName, formatted = ""; 26620 26621 try { 26622 for (var field in PhoneNumber._fieldOrder) { 26623 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') { 26624 fieldName = PhoneNumber._fieldOrder[field]; 26625 // console.info("normalize: formatting field " + fieldName); 26626 if (this[fieldName] !== undefined) { 26627 formatted += this[fieldName]; 26628 } 26629 } 26630 } 26631 } catch ( e ) { 26632 //console.warn("caught exception in _join: " + e); 26633 throw e; 26634 } 26635 return formatted; 26636 }, 26637 26638 /** 26639 * This routine will compare the two phone numbers in an locale-sensitive 26640 * manner to see if they possibly reference the same phone number.<p> 26641 * 26642 * In many places, 26643 * there are multiple ways to reach the same phone number. In North America for 26644 * example, you might have a number with the trunk access code of "1" and another 26645 * without, and they reference the exact same phone number. This is considered a 26646 * strong match. For a different pair of numbers, one may be a local number and 26647 * the other a full phone number with area code, which may reference the same 26648 * phone number if the local number happens to be located in that area code. 26649 * However, you cannot say for sure if it is in that area code, so it will 26650 * be considered a somewhat weaker match.<p> 26651 * 26652 * Similarly, in other countries, there are sometimes different ways of 26653 * reaching the same destination, and the way that numbers 26654 * match depends on the locale.<p> 26655 * 26656 * The various phone number fields are handled differently for matches. There 26657 * are various fields that do not need to match at all. For example, you may 26658 * type equally enter "00" or "+" into your phone to start international direct 26659 * dialling, so the iddPrefix field does not need to match at all.<p> 26660 * 26661 * Typically, fields that require matches need to match exactly if both sides have a value 26662 * for that field. If both sides specify a value and those values differ, that is 26663 * a strong non-match. If one side does not have a value and the other does, that 26664 * causes a partial match, because the number with the missing field may possibly 26665 * have an implied value that matches the other number. For example, the numbers 26666 * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234" 26667 * might possibly have the same 650 area code as the first number, and might possibly 26668 * not. If both side do not specify a value for a particular field, that field is 26669 * considered matching.<p> 26670 * 26671 * The values of following fields are ignored when performing matches: 26672 * 26673 * <ul> 26674 * <li>vsc 26675 * <li>iddPrefix 26676 * <li>cic 26677 * <li>trunkAccess 26678 * </ul> 26679 * 26680 * The values of the following fields matter if they do not match: 26681 * 26682 * <ul> 26683 * <li>countryCode - A difference causes a moderately strong problem except for 26684 * certain countries where there is a way to access the same subscriber via IDD 26685 * and via intranetwork dialling 26686 * <li>mobilePrefix - A difference causes a possible non-match 26687 * <li>serviceCode - A difference causes a possible non-match 26688 * <li>areaCode - A difference causes a possible non-match 26689 * <li>subscriberNumber - A difference causes a very strong non-match 26690 * <li>extension - A difference causes a minor non-match 26691 * </ul> 26692 * 26693 * @param {string|PhoneNumber} other other phone number to compare this one to 26694 * @return {number} non-negative integer describing the percentage quality of the 26695 * match. 100 means a very strong match (100%), and lower numbers are less and 26696 * less strong, down to 0 meaning not at all a match. 26697 */ 26698 compare: function (other) { 26699 var match = 100, 26700 FRdepartments = {"590":1, "594":1, "596":1, "262":1}, 26701 ITcountries = {"378":1, "379":1}, 26702 thisPrefix, 26703 otherPrefix, 26704 currentCountryCode = 0; 26705 26706 if (typeof this.locale.region === "string") { 26707 currentCountryCode = this.locale._mapRegiontoCC(this.locale.region); 26708 } 26709 26710 // subscriber number must be present and must match 26711 if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) { 26712 return 0; 26713 } 26714 26715 // extension must match if it is present 26716 if (this._xor(this.extension, other.extension) || this.extension !== other.extension) { 26717 return 0; 26718 } 26719 26720 if (this._xor(this.countryCode, other.countryCode)) { 26721 // if one doesn't have a country code, give it some demerit points, but if the 26722 // one that has the country code has something other than the current country 26723 // add even more. Ignore the special cases where you can dial the same number internationally or via 26724 // the local numbering system 26725 switch (this.locale.getRegion()) { 26726 case 'FR': 26727 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 26728 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 26729 match -= 100; 26730 } 26731 } else { 26732 match -= 16; 26733 } 26734 break; 26735 case 'IT': 26736 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 26737 if (this.areaCode !== other.areaCode) { 26738 match -= 100; 26739 } 26740 } else { 26741 match -= 16; 26742 } 26743 break; 26744 default: 26745 match -= 16; 26746 if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) || 26747 (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) { 26748 match -= 16; 26749 } 26750 } 26751 } else if (this.countryCode !== other.countryCode) { 26752 // ignore the special cases where you can dial the same number internationally or via 26753 // the local numbering system 26754 if (other.countryCode === '33' || this.countryCode === '33') { 26755 // france 26756 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 26757 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 26758 match -= 100; 26759 } 26760 } else { 26761 match -= 100; 26762 } 26763 } else if (this.countryCode === '39' || other.countryCode === '39') { 26764 // italy 26765 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 26766 if (this.areaCode !== other.areaCode) { 26767 match -= 100; 26768 } 26769 } else { 26770 match -= 100; 26771 } 26772 } else { 26773 match -= 100; 26774 } 26775 } 26776 26777 if (this._xor(this.serviceCode, other.serviceCode)) { 26778 match -= 20; 26779 } else if (this.serviceCode !== other.serviceCode) { 26780 match -= 100; 26781 } 26782 26783 if (this._xor(this.mobilePrefix, other.mobilePrefix)) { 26784 match -= 20; 26785 } else if (this.mobilePrefix !== other.mobilePrefix) { 26786 match -= 100; 26787 } 26788 26789 if (this._xor(this.areaCode, other.areaCode)) { 26790 // one has an area code, the other doesn't, so dock some points. It could be a match if the local 26791 // number in the one number has the same implied area code as the explicit area code in the other number. 26792 match -= 12; 26793 } else if (this.areaCode !== other.areaCode) { 26794 match -= 100; 26795 } 26796 26797 thisPrefix = this._getPrefix(); 26798 otherPrefix = other._getPrefix(); 26799 26800 if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) { 26801 match -= 100; 26802 } 26803 26804 // make sure we are between 0 and 100 26805 if (match < 0) { 26806 match = 0; 26807 } else if (match > 100) { 26808 match = 100; 26809 } 26810 26811 return match; 26812 }, 26813 26814 /** 26815 * Determine whether or not the other phone number is exactly equal to the current one.<p> 26816 * 26817 * The difference between the compare method and the equals method is that the compare 26818 * method compares normalized numbers with each other and returns the degree of match, 26819 * whereas the equals operator returns true iff the two numbers contain the same fields 26820 * and the fields are exactly the same. Functions and other non-phone number properties 26821 * are not compared. 26822 * @param {string|PhoneNumber} other another phone number to compare to this one 26823 * @return {boolean} true if the numbers are the same, false otherwise 26824 */ 26825 equals: function equals(other) { 26826 if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) { 26827 return false; 26828 } 26829 26830 for (var p in other) { 26831 if (p !== undefined && this[p] !== undefined && typeof(this[p]) !== 'object') { 26832 if (other[p] === undefined) { 26833 /*console.error("PhoneNumber.equals: other is missing property " + p + " which has the value " + this[p] + " in this"); 26834 console.error("this is : " + JSON.stringify(this)); 26835 console.error("other is: " + JSON.stringify(other));*/ 26836 return false; 26837 } 26838 if (this[p] !== other[p]) { 26839 /*console.error("PhoneNumber.equals: difference in property " + p); 26840 console.error("this is : " + JSON.stringify(this)); 26841 console.error("other is: " + JSON.stringify(other));*/ 26842 return false; 26843 } 26844 } 26845 } 26846 for (var p in other) { 26847 if (p !== undefined && other[p] !== undefined && typeof(other[p]) !== 'object') { 26848 if (this[p] === undefined) { 26849 /*console.error("PhoneNumber.equals: this is missing property " + p + " which has the value " + other[p] + " in the other"); 26850 console.error("this is : " + JSON.stringify(this)); 26851 console.error("other is: " + JSON.stringify(other));*/ 26852 return false; 26853 } 26854 if (this[p] !== other[p]) { 26855 /*console.error("PhoneNumber.equals: difference in property " + p); 26856 console.error("this is : " + JSON.stringify(this)); 26857 console.error("other is: " + JSON.stringify(other));*/ 26858 return false; 26859 } 26860 } 26861 } 26862 return true; 26863 }, 26864 26865 26866 /** 26867 * @private 26868 * @param {{ 26869 * mcc:string, 26870 * defaultAreaCode:string, 26871 * country:string, 26872 * networkType:string, 26873 * assistedDialing:boolean, 26874 * sms:boolean, 26875 * manualDialing:boolean 26876 * }} options an object containing options to help in normalizing. 26877 * @param {PhoneNumber} norm 26878 * @param {PhoneLocale} homeLocale 26879 * @param {PhoneLocale} currentLocale 26880 * @param {NumberingPlan} currentPlan 26881 * @param {PhoneLocale} destinationLocale 26882 * @param {NumberingPlan} destinationPlan 26883 * @param {boolean} sync 26884 * @param {Object|undefined} loadParams 26885 */ 26886 _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) { 26887 var formatted = ""; 26888 26889 if (!norm.invalid && options && options.assistedDialing) { 26890 // don't normalize things that don't have subscriber numbers. Also, don't normalize 26891 // manually dialed local numbers. Do normalize local numbers in contact entries. 26892 if (norm.subscriberNumber && 26893 (!options.manualDialing || 26894 norm.iddPrefix || 26895 norm.countryCode || 26896 norm.trunkAccess)) { 26897 // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm)); 26898 if (currentLocale.getRegion() !== destinationLocale.getRegion()) { 26899 // we are currently calling internationally 26900 if (!norm._hasPrefix() && 26901 options.defaultAreaCode && 26902 destinationLocale.getRegion() === homeLocale.getRegion() && 26903 (!destinationPlan.getFieldLength("minLocalLength") || 26904 norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) { 26905 // area code is required when dialling from international, but only add it if we are dialing 26906 // to our home area. Otherwise, the default area code is not valid! 26907 norm.areaCode = options.defaultAreaCode; 26908 if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) { 26909 // some phone systems require the trunk access code, even when dialling from international 26910 norm.trunkAccess = destinationPlan.getTrunkCode(); 26911 } 26912 } 26913 26914 if (norm.trunkAccess && destinationPlan.getSkipTrunk()) { 26915 // on some phone systems, the trunk access code is dropped when dialling from international 26916 delete norm.trunkAccess; 26917 } 26918 26919 // make sure to get the country code for the destination region, not the current region! 26920 if (options.sms) { 26921 if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") { 26922 if (destinationLocale.getRegion() !== "US") { 26923 norm.iddPrefix = "011"; // non-standard code to make it go through the US first 26924 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 26925 } else if (options.networkType === "cdma") { 26926 delete norm.iddPrefix; 26927 delete norm.countryCode; 26928 if (norm.areaCode) { 26929 norm.trunkAccess = "1"; 26930 } 26931 } else if (norm.areaCode) { 26932 norm.iddPrefix = "+"; 26933 norm.countryCode = "1"; 26934 delete norm.trunkAccess; 26935 } 26936 } else { 26937 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 26938 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region); 26939 } 26940 } else if (norm._hasPrefix() && !norm.countryCode) { 26941 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region); 26942 } 26943 26944 if (norm.countryCode && !options.sms) { 26945 // for CDMA, make sure to get the international dialling access code for the current region, not the destination region 26946 // all umts carriers support plus dialing 26947 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 26948 } 26949 } else { 26950 // console.log("normalize: dialing within the country"); 26951 if (options.defaultAreaCode) { 26952 if (destinationPlan.getPlanStyle() === "open") { 26953 if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) { 26954 // call is not local to this area code, so you have to dial the trunk code and the area code 26955 norm.trunkAccess = destinationPlan.getTrunkCode(); 26956 } 26957 } else { 26958 // In closed plans, you always have to dial the area code, even if the call is local. 26959 if (!norm._hasPrefix()) { 26960 if (destinationLocale.getRegion() === homeLocale.getRegion()) { 26961 norm.areaCode = options.defaultAreaCode; 26962 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 26963 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 26964 } 26965 } 26966 } else { 26967 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 26968 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 26969 } 26970 } 26971 } 26972 } 26973 26974 if (options.sms && 26975 homeLocale.getRegion() === "US" && 26976 currentLocale.getRegion() !== "US") { 26977 norm.iddPrefix = "011"; // make it go through the US first 26978 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 26979 delete norm.trunkAccess; 26980 } 26981 } else if (norm.iddPrefix || norm.countryCode) { 26982 // we are in our destination country, so strip the international dialling prefixes 26983 delete norm.iddPrefix; 26984 delete norm.countryCode; 26985 26986 if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) { 26987 norm.trunkAccess = destinationPlan.getTrunkCode(); 26988 } 26989 } 26990 } 26991 } 26992 } else if (!norm.invalid) { 26993 // console.log("normalize: non-assisted normalization"); 26994 if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) { 26995 norm.areaCode = options.defaultAreaCode; 26996 } 26997 26998 if (!norm.countryCode && norm._hasPrefix()) { 26999 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 27000 } 27001 27002 if (norm.countryCode) { 27003 if (options && options.networkType && options.networkType === "cdma") { 27004 norm.iddPrefix = currentPlan.getIDDCode(); 27005 } else { 27006 // all umts carriers support plus dialing 27007 norm.iddPrefix = "+"; 27008 } 27009 27010 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 27011 delete norm.trunkAccess; 27012 } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) { 27013 norm.trunkAccess = destinationPlan.getTrunkCode(); 27014 } 27015 } 27016 } 27017 27018 // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm)); 27019 formatted = norm._join(); 27020 27021 return formatted; 27022 }, 27023 27024 /** 27025 * @private 27026 * @param {{ 27027 * mcc:string, 27028 * defaultAreaCode:string, 27029 * country:string, 27030 * networkType:string, 27031 * assistedDialing:boolean, 27032 * sms:boolean, 27033 * manualDialing:boolean 27034 * }} options an object containing options to help in normalizing. 27035 * @param {PhoneNumber} norm 27036 * @param {PhoneLocale} homeLocale 27037 * @param {PhoneLocale} currentLocale 27038 * @param {NumberingPlan} currentPlan 27039 * @param {PhoneLocale} destinationLocale 27040 * @param {NumberingPlan} destinationPlan 27041 * @param {boolean} sync 27042 * @param {Object|undefined} loadParams 27043 * @param {function(string)} callback 27044 */ 27045 _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) { 27046 var formatted, 27047 tempRegion; 27048 27049 if (options && 27050 options.assistedDialing && 27051 !norm.trunkAccess && 27052 !norm.iddPrefix && 27053 norm.subscriberNumber && 27054 norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) { 27055 27056 // numbers that are too long are sometimes international direct dialed numbers that 27057 // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works. 27058 new PhoneNumber("+" + this._join(), { 27059 locale: this.locale, 27060 sync: sync, 27061 loadParms: loadParams, 27062 onLoad: ilib.bind(this, function (data) { 27063 tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode)); 27064 27065 if (tempRegion && tempRegion !== "unknown" && tempRegion !== "SG") { 27066 // only use it if it is a recognized country code. Singapore (SG) is a special case. 27067 norm = data; 27068 destinationLocale = data.destinationLocale; 27069 destinationPlan = data.destinationPlan; 27070 } 27071 27072 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 27073 if (typeof(callback) === 'function') { 27074 callback(formatted); 27075 } 27076 }) 27077 }); 27078 } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) { 27079 // if this number is not valid for the locale it was parsed with, try again with the current locale 27080 // console.log("norm is invalid. Attempting to reparse with the current locale"); 27081 27082 new PhoneNumber(this._join(), { 27083 locale: currentLocale, 27084 sync: sync, 27085 loadParms: loadParams, 27086 onLoad: ilib.bind(this, function (data) { 27087 if (data && !data.invalid) { 27088 norm = data; 27089 } 27090 27091 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 27092 if (typeof(callback) === 'function') { 27093 callback(formatted); 27094 } 27095 }) 27096 }); 27097 } else { 27098 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 27099 if (typeof(callback) === 'function') { 27100 callback(formatted); 27101 } 27102 } 27103 }, 27104 27105 /** 27106 * This function normalizes the current phone number to a canonical format and returns a 27107 * string with that phone number. If parts are missing, this function attempts to fill in 27108 * those parts.<p> 27109 * 27110 * The options object contains a set of properties that can possibly help normalize 27111 * this number by providing "extra" information to the algorithm. The options 27112 * parameter may be null or an empty object if no hints can be determined before 27113 * this call is made. If any particular hint is not 27114 * available, it does not need to be present in the options object.<p> 27115 * 27116 * The following is a list of hints that the algorithm will look for in the options 27117 * object: 27118 * 27119 * <ul> 27120 * <li><i>mcc</i> the mobile carrier code of the current network upon which this 27121 * phone is operating. This is translated into an IDD country code. This is 27122 * useful if the number being normalized comes from CNAP (callerid) and the 27123 * MCC is known. 27124 * <li><i>defaultAreaCode</i> the area code of the phone number of the current 27125 * device, if available. Local numbers in a person's contact list are most 27126 * probably in this same area code. 27127 * <li><i>country</i> the 2 letter ISO 3166 code of the country if it is 27128 * known from some other means such as parsing the physical address of the 27129 * person associated with the phone number, or the from the domain name 27130 * of the person's email address 27131 * <li><i>networkType</i> specifies whether the phone is currently connected to a 27132 * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts". 27133 * If one of those two strings are not specified, or if this property is left off 27134 * completely, this method will assume UMTS. 27135 * </ul> 27136 * 27137 * The following are a list of options that control the behaviour of the normalization: 27138 * 27139 * <ul> 27140 * <li><i>assistedDialing</i> if this is set to true, the number will be normalized 27141 * so that it can dialled directly on the type of network this phone is 27142 * currently connected to. This allows customers to dial numbers or use numbers 27143 * in their contact list that are specific to their "home" region when they are 27144 * roaming and those numbers would not otherwise work with the current roaming 27145 * carrier as they are. The home region is 27146 * specified as the phoneRegion system preference that is settable in the 27147 * regional settings app. With assisted dialling, this method will add or 27148 * remove international direct dialling prefixes and country codes, as well as 27149 * national trunk access codes, as required by the current roaming carrier and the 27150 * home region in order to dial the number properly. If it is not possible to 27151 * construct a full international dialling sequence from the options and hints given, 27152 * this function will not modify the phone number, and will return "undefined". 27153 * If assisted dialling is false or not specified, then this method will attempt 27154 * to add all the information it can to the number so that it is as fully 27155 * specified as possible. This allows two numbers to be compared more easily when 27156 * those two numbers were otherwise only partially specified. 27157 * <li><i>sms</i> set this option to true for the following conditions: 27158 * <ul> 27159 * <li>assisted dialing is turned on 27160 * <li>the phone number represents the destination of an SMS message 27161 * <li>the phone is UMTS 27162 * <li>the phone is SIM-locked to its carrier 27163 * </ul> 27164 * This enables special international direct dialling codes to route the SMS message to 27165 * the correct carrier. If assisted dialling is not turned on, this option has no 27166 * affect. 27167 * <li><i>manualDialing</i> set this option to true if the user is entering this number on 27168 * the keypad directly, and false when the number comes from a stored location like a 27169 * contact entry or a call log entry. When true, this option causes the normalizer to 27170 * not perform any normalization on numbers that look like local numbers in the home 27171 * country. If false, all numbers go through normalization. This option only has an effect 27172 * when the assistedDialing option is true as well, otherwise it is ignored. 27173 * </ul> 27174 * 27175 * If both a set of options and a locale are given, and they offer conflicting 27176 * information, the options will take precedence. The idea is that the locale 27177 * tells you the region setting that the user has chosen (probably in 27178 * firstuse), whereas the the hints are more current information such as 27179 * where the phone is currently operating (the MCC).<p> 27180 * 27181 * This function performs the following types of normalizations with assisted 27182 * dialling turned on: 27183 * 27184 * <ol> 27185 * <li>If the current location of the phone matches the home country, this is a 27186 * domestic call. 27187 * <ul> 27188 * <li>Remove any iddPrefix and countryCode fields, as they are not needed 27189 * <li>Add in a trunkAccess field that may be necessary to call a domestic numbers 27190 * in the home country 27191 * </ul> 27192 * <li> If the current location of the phone does not match the home country, 27193 * attempt to form a whole international number. 27194 * <ul> 27195 * <li>Add in the area code if it is missing from the phone number and the area code 27196 * of the current phone is available in the hints 27197 * <li>Add the country dialling code for the home country if it is missing from the 27198 * phone number 27199 * <li>Add or replace the iddPrefix with the correct one for the current country. The 27200 * phone number will have been parsed with the settings for the home country, so 27201 * the iddPrefix may be incorrect for the 27202 * current country. The iddPrefix for the current country can be "+" if the phone 27203 * is connected to a UMTS network, and either a "+" or a country-dependent 27204 * sequences of digits for CDMA networks. 27205 * </ul> 27206 * </ol> 27207 * 27208 * This function performs the following types of normalization with assisted 27209 * dialling turned off: 27210 * 27211 * <ul> 27212 * <li>Normalize the international direct dialing prefix to be a plus or the 27213 * international direct dialling access code for the current country, depending 27214 * on the network type. 27215 * <li>If a number is a local number (ie. it is missing its area code), 27216 * use a default area code from the hints if available. CDMA phones always know their area 27217 * code, and GSM/UMTS phones know their area code in many instances, but not always 27218 * (ie. not on Vodaphone or Telcel phones). If the default area code is not available, 27219 * do not add it. 27220 * <li>In assisted dialling mode, if a number is missing its country code, 27221 * use the current MCC number if 27222 * it is available to figure out the current country code, and prepend that 27223 * to the number. If it is not available, leave it off. Also, use that 27224 * country's settings to parse the number instead of the current format 27225 * locale. 27226 * <li>For North American numbers with an area code but no trunk access 27227 * code, add in the trunk access code. 27228 * <li>For other countries, if the country code is added in step 3, remove the 27229 * trunk access code when required by that country's conventions for 27230 * international calls. If the country requires a trunk access code for 27231 * international calls and it doesn't exist, add one. 27232 * </ul> 27233 * 27234 * This method modifies the current object, and also returns a string 27235 * containing the normalized phone number that can be compared directly against 27236 * other normalized numbers. The canonical format for phone numbers that is 27237 * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string 27238 * of dialable digits. 27239 * 27240 * @param {{ 27241 * mcc:string, 27242 * defaultAreaCode:string, 27243 * country:string, 27244 * networkType:string, 27245 * assistedDialing:boolean, 27246 * sms:boolean, 27247 * manualDialing:boolean 27248 * }} options an object containing options to help in normalizing. 27249 * @return {string|undefined} the normalized string, or undefined if the number 27250 * could not be normalized 27251 */ 27252 normalize: function(options) { 27253 var norm, 27254 sync = true, 27255 loadParams = {}; 27256 27257 27258 if (options) { 27259 if (typeof(options.sync) !== 'undefined') { 27260 sync = (options.sync == true); 27261 } 27262 27263 if (options.loadParams) { 27264 loadParams = options.loadParams; 27265 } 27266 } 27267 27268 // Clone this number, so we don't mess with the original. 27269 // No need to do this asynchronously because it's a copy constructor which doesn't 27270 // load any extra files. 27271 norm = new PhoneNumber(this); 27272 27273 var normalized; 27274 27275 if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) { 27276 new PhoneLocale({ 27277 mcc: options.mcc, 27278 countryCode: options.countryCode, 27279 locale: this.locale, 27280 sync: sync, 27281 loadParams: loadParams, 27282 onLoad: ilib.bind(this, function(loc) { 27283 new NumberingPlan({ 27284 locale: loc, 27285 sync: sync, 27286 loadParms: loadParams, 27287 onLoad: ilib.bind(this, function (plan) { 27288 this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 27289 normalized = fmt; 27290 27291 if (options && typeof(options.onLoad) === 'function') { 27292 options.onLoad(fmt); 27293 } 27294 }); 27295 }) 27296 }); 27297 }) 27298 }); 27299 } else { 27300 this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 27301 normalized = fmt; 27302 27303 if (options && typeof(options.onLoad) === 'function') { 27304 options.onLoad(fmt); 27305 } 27306 }); 27307 } 27308 27309 // return the value for the synchronous case 27310 return normalized; 27311 } 27312 }; 27313 27314 27315 /*< PhoneFmt.js */ 27316 /* 27317 * phonefmt.js - Represent a phone number formatter. 27318 * 27319 * Copyright © 2014-2015, JEDLSoft 27320 * 27321 * Licensed under the Apache License, Version 2.0 (the "License"); 27322 * you may not use this file except in compliance with the License. 27323 * You may obtain a copy of the License at 27324 * 27325 * http://www.apache.org/licenses/LICENSE-2.0 27326 * 27327 * Unless required by applicable law or agreed to in writing, software 27328 * distributed under the License is distributed on an "AS IS" BASIS, 27329 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27330 * 27331 * See the License for the specific language governing permissions and 27332 * limitations under the License. 27333 */ 27334 27335 /* 27336 !depends 27337 ilib.js 27338 Locale.js 27339 NumberingPlan.js 27340 PhoneNumber.js 27341 PhoneLocale.js 27342 Utils.js 27343 JSUtils.js 27344 */ 27345 27346 // !data phonefmt 27347 27348 27349 /** 27350 * @class 27351 * Create a new phone number formatter object that formats numbers according to the parameters.<p> 27352 * 27353 * The options object can contain zero or more of the following parameters: 27354 * 27355 * <ul> 27356 * <li><i>locale</i> locale to use to format this number, or undefined to use the default locale 27357 * <li><i>style</i> the name of style to use to format numbers, or undefined to use the default style 27358 * <li><i>mcc</i> the MCC of the country to use if the number is a local number and the country code is not known 27359 * 27360 * <li><i>onLoad</i> - a callback function to call when the locale data is fully loaded and the address has been 27361 * parsed. When the onLoad option is given, the address formatter object 27362 * will attempt to load any missing locale data using the ilib loader callback. 27363 * When the constructor is done (even if the data is already preassembled), the 27364 * onLoad function is called with the current instance as a parameter, so this 27365 * callback can be used with preassembled or dynamic loading or a mix of the two. 27366 * 27367 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27368 * asynchronously. If this option is given as "false", then the "onLoad" 27369 * callback must be given, as the instance returned from this constructor will 27370 * not be usable for a while. 27371 * 27372 * <li><i>loadParams</i> - an object containing parameters to pass to the 27373 * loader callback function when locale data is missing. The parameters are not 27374 * interpretted or modified in any way. They are simply passed along. The object 27375 * may contain any property/value pairs as long as the calling code is in 27376 * agreement with the loader callback function as to what those parameters mean. 27377 * </ul> 27378 * 27379 * Some regions have more than one style of formatting, and the style parameter 27380 * selects which style the user prefers. An array of style names that this locale 27381 * supports can be found by calling {@link PhoneFmt.getAvailableStyles}. 27382 * Example phone numbers can be retrieved for each style by calling 27383 * {@link PhoneFmt.getStyleExample}. 27384 * <p> 27385 * 27386 * If the MCC is given, numbers will be formatted in the manner of the country 27387 * specified by the MCC. If it is not given, but the locale is, the manner of 27388 * the country in the locale will be used. If neither the locale or MCC are not given, 27389 * then the country of the current ilib locale is used. 27390 * 27391 * @constructor 27392 * @param {Object} options properties that control how this formatter behaves 27393 */ 27394 var PhoneFmt = function(options) { 27395 this.sync = true; 27396 this.styleName = 'default', 27397 this.loadParams = {}; 27398 27399 var locale = new Locale(); 27400 27401 if (options) { 27402 if (options.locale) { 27403 locale = options.locale; 27404 } 27405 27406 if (typeof(options.sync) !== 'undefined') { 27407 this.sync = (options.sync == true); 27408 } 27409 27410 if (options.loadParams) { 27411 this.loadParams = options.loadParams; 27412 } 27413 27414 if (options.style) { 27415 this.style = options.style; 27416 } 27417 } 27418 27419 new PhoneLocale({ 27420 locale: locale, 27421 mcc: options && options.mcc, 27422 countryCode: options && options.countryCode, 27423 onLoad: ilib.bind(this, function (data) { 27424 /** @type {PhoneLocale} */ 27425 this.locale = data; 27426 27427 new NumberingPlan({ 27428 locale: this.locale, 27429 sync: this.sync, 27430 loadParms: this.loadParams, 27431 onLoad: ilib.bind(this, function (plan) { 27432 /** @type {NumberingPlan} */ 27433 this.plan = plan; 27434 27435 Utils.loadData({ 27436 name: "phonefmt.json", 27437 object: PhoneFmt, 27438 locale: this.locale, 27439 sync: this.sync, 27440 loadParams: JSUtils.merge(this.loadParams, { 27441 returnOne: true 27442 }), 27443 callback: ilib.bind(this, function (fmtdata) { 27444 this.fmtdata = fmtdata; 27445 27446 if (options && typeof(options.onLoad) === 'function') { 27447 options.onLoad(this); 27448 } 27449 }) 27450 }); 27451 }) 27452 }); 27453 }) 27454 }); 27455 }; 27456 27457 PhoneFmt.prototype = { 27458 /** 27459 * 27460 * @protected 27461 * @param {string} part 27462 * @param {Object} formats 27463 * @param {boolean} mustUseAll 27464 */ 27465 _substituteDigits: function(part, formats, mustUseAll) { 27466 var formatString, 27467 formatted = "", 27468 partIndex = 0, 27469 templates, 27470 i; 27471 27472 // console.info("Globalization.Phone._substituteDigits: typeof(formats) is " + typeof(formats)); 27473 if (!part) { 27474 return formatted; 27475 } 27476 27477 if (typeof(formats) === "object") { 27478 templates = (typeof(formats.template) !== 'undefined') ? formats.template : formats; 27479 if (part.length > templates.length) { 27480 // too big, so just use last resort rule. 27481 throw "part " + part + " is too big. We do not have a format template to format it."; 27482 } 27483 // use the format in this array that corresponds to the digit length of this 27484 // part of the phone number 27485 formatString = templates[part.length-1]; 27486 // console.info("Globalization.Phone._substituteDigits: formats is an Array: " + JSON.stringify(formats)); 27487 } else { 27488 formatString = formats; 27489 } 27490 27491 for (i = 0; i < formatString.length; i++) { 27492 if (formatString.charAt(i) === "X") { 27493 formatted += part.charAt(partIndex); 27494 partIndex++; 27495 } else { 27496 formatted += formatString.charAt(i); 27497 } 27498 } 27499 27500 if (mustUseAll && partIndex < part.length-1) { 27501 // didn't use the whole thing in this format? Hmm... go to last resort rule 27502 throw "too many digits in " + part + " for format " + formatString; 27503 } 27504 27505 return formatted; 27506 }, 27507 27508 /** 27509 * Returns the style with the given name, or the default style if there 27510 * is no style with that name. 27511 * @protected 27512 * @return {{example:string,whole:Object.<string,string>,partial:Object.<string,string>}|Object.<string,string>} 27513 */ 27514 _getStyle: function (name, fmtdata) { 27515 return fmtdata[name] || fmtdata["default"]; 27516 }, 27517 27518 /** 27519 * Do the actual work of formatting the phone number starting at the given 27520 * field in the regular field order. 27521 * 27522 * @param {!PhoneNumber} number 27523 * @param {{ 27524 * partial:boolean, 27525 * style:string, 27526 * mcc:string, 27527 * locale:(string|Locale), 27528 * sync:boolean, 27529 * loadParams:Object, 27530 * onLoad:function(string) 27531 * }} options Parameters which control how to format the number 27532 * @param {number} startField 27533 */ 27534 _doFormat: function(number, options, startField, locale, fmtdata, callback) { 27535 var sync = true, 27536 loadParams = {}, 27537 temp, 27538 templates, 27539 fieldName, 27540 countryCode, 27541 isWhole, 27542 style, 27543 formatted = "", 27544 styleTemplates, 27545 lastFieldName; 27546 27547 if (options) { 27548 if (typeof(options.sync) !== 'undefined') { 27549 sync = (options.sync == true); 27550 } 27551 27552 if (options.loadParams) { 27553 loadParams = options.loadParams; 27554 } 27555 } 27556 27557 style = this.style; // default style for this formatter 27558 27559 // figure out what style to use for this type of number 27560 if (number.countryCode) { 27561 // dialing from outside the country 27562 // check to see if it to a mobile number because they are often formatted differently 27563 style = (number.mobilePrefix) ? "internationalmobile" : "international"; 27564 } else if (number.mobilePrefix !== undefined) { 27565 style = "mobile"; 27566 } else if (number.serviceCode !== undefined && typeof(fmtdata["service"]) !== 'undefined') { 27567 // if there is a special format for service numbers, then use it 27568 style = "service"; 27569 } 27570 27571 isWhole = (!options || !options.partial); 27572 styleTemplates = this._getStyle(style, fmtdata); 27573 27574 // console.log("Style ends up being " + style + " and using subtype " + (isWhole ? "whole" : "partial")); 27575 styleTemplates = (isWhole ? styleTemplates.whole : styleTemplates.partial) || styleTemplates; 27576 27577 for (var i = startField; i < PhoneNumber._fieldOrder.length; i++) { 27578 fieldName = PhoneNumber._fieldOrder[i]; 27579 // console.info("format: formatting field " + fieldName + " value: " + number[fieldName]); 27580 if (number[fieldName] !== undefined) { 27581 if (styleTemplates[fieldName] !== undefined) { 27582 templates = styleTemplates[fieldName]; 27583 if (fieldName === "trunkAccess") { 27584 if (number.areaCode === undefined && number.serviceCode === undefined && number.mobilePrefix === undefined) { 27585 templates = "X"; 27586 } 27587 } 27588 if (lastFieldName && typeof(styleTemplates[lastFieldName].suffix) !== 'undefined') { 27589 if (fieldName !== "extension" && number[fieldName].search(/[xwtp,;]/i) <= -1) { 27590 formatted += styleTemplates[lastFieldName].suffix; 27591 } 27592 } 27593 lastFieldName = fieldName; 27594 27595 // console.info("format: formatting field " + fieldName + " with templates " + JSON.stringify(templates)); 27596 temp = this._substituteDigits(number[fieldName], templates, (fieldName === "subscriberNumber")); 27597 // console.info("format: formatted is: " + temp); 27598 formatted += temp; 27599 27600 if (fieldName === "countryCode") { 27601 // switch to the new country to format the rest of the number 27602 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 27603 27604 new PhoneLocale({ 27605 locale: this.locale, 27606 sync: sync, 27607 loadParms: loadParams, 27608 countryCode: countryCode, 27609 onLoad: ilib.bind(this, function (locale) { 27610 Utils.loadData({ 27611 name: "phonefmt.json", 27612 object: PhoneFmt, 27613 locale: locale, 27614 sync: sync, 27615 loadParams: JSUtils.merge(loadParams, { 27616 returnOne: true 27617 }), 27618 callback: ilib.bind(this, function (fmtdata) { 27619 // console.info("format: switching to region " + locale.region + " and style " + style + " to format the rest of the number "); 27620 27621 var subfmt = ""; 27622 27623 this._doFormat(number, options, i+1, locale, fmtdata, function (subformat) { 27624 subfmt = subformat; 27625 if (typeof(callback) === 'function') { 27626 callback(formatted + subformat); 27627 } 27628 }); 27629 27630 formatted += subfmt; 27631 }) 27632 }); 27633 }) 27634 }); 27635 return formatted; 27636 } 27637 } else { 27638 //console.warn("PhoneFmt.format: cannot find format template for field " + fieldName + ", region " + locale.region + ", style " + style); 27639 // use default of "minimal formatting" so we don't miss parts because of bugs in the format templates 27640 formatted += number[fieldName]; 27641 } 27642 } 27643 } 27644 27645 if (typeof(callback) === 'function') { 27646 callback(formatted); 27647 } 27648 27649 return formatted; 27650 }, 27651 27652 /** 27653 * Format the parts of a phone number appropriately according to the settings in 27654 * this formatter instance. 27655 * 27656 * The options can contain zero or more of these properties: 27657 * 27658 * <ul> 27659 * <li><i>partial</i> boolean which tells whether or not this phone number 27660 * represents a partial number or not. The default is false, which means the number 27661 * represents a whole number. 27662 * <li><i>style</i> style to use to format the number, if different from the 27663 * default style or the style specified in the constructor 27664 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 27665 * numbering plan to use. 27666 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 27667 * currently connected to, if known. This also can give a clue as to which numbering plan to 27668 * use 27669 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 27670 * loaded. When the onLoad option is given, the DateFmt object will attempt to 27671 * load any missing locale data using the ilib loader callback. 27672 * When the constructor is done (even if the data is already preassembled), the 27673 * onLoad function is called with the current instance as a parameter, so this 27674 * callback can be used with preassembled or dynamic loading or a mix of the two. 27675 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27676 * asynchronously. If this option is given as "false", then the "onLoad" 27677 * callback must be given, as the instance returned from this constructor will 27678 * not be usable for a while. 27679 * <li><i>loadParams</i> - an object containing parameters to pass to the 27680 * loader callback function when locale data is missing. The parameters are not 27681 * interpretted or modified in any way. They are simply passed along. The object 27682 * may contain any property/value pairs as long as the calling code is in 27683 * agreement with the loader callback function as to what those parameters mean. 27684 * </ul> 27685 * 27686 * The partial parameter specifies whether or not the phone number contains 27687 * a partial phone number or if it is a whole phone number. A partial 27688 * number is usually a number as the user is entering it with a dial pad. The 27689 * reason is that certain types of phone numbers should be formatted differently 27690 * depending on whether or not it represents a whole number. Specifically, SMS 27691 * short codes are formatted differently.<p> 27692 * 27693 * Example: a subscriber number of "48773" in the US would get formatted as: 27694 * 27695 * <ul> 27696 * <li>partial: 487-73 (perhaps the user is in the process of typing a whole phone 27697 * number such as 487-7379) 27698 * <li>whole: 48773 (this is the entire SMS short code) 27699 * </ul> 27700 * 27701 * Any place in the UI where the user types in phone numbers, such as the keypad in 27702 * the phone app, should pass in partial: true to this formatting routine. All other 27703 * places, such as the call log in the phone app, should pass in partial: false, or 27704 * leave the partial flag out of the parameters entirely. 27705 * 27706 * @param {!PhoneNumber} number object containing the phone number to format 27707 * @param {{ 27708 * partial:boolean, 27709 * style:string, 27710 * mcc:string, 27711 * locale:(string|Locale), 27712 * sync:boolean, 27713 * loadParams:Object, 27714 * onLoad:function(string) 27715 * }} options Parameters which control how to format the number 27716 * @return {string} Returns the formatted phone number as a string. 27717 */ 27718 format: function (number, options) { 27719 var formatted = "", 27720 callback; 27721 27722 callback = options && options.onLoad; 27723 27724 try { 27725 this._doFormat(number, options, 0, this.locale, this.fmtdata, function (fmt) { 27726 formatted = fmt; 27727 27728 if (typeof(callback) === 'function') { 27729 callback(fmt); 27730 } 27731 }); 27732 } catch (e) { 27733 if (typeof(e) === 'string') { 27734 // console.warn("caught exception: " + e + ". Using last resort rule."); 27735 // if there was some exception, use this last resort rule 27736 formatted = ""; 27737 for (var field in PhoneNumber._fieldOrder) { 27738 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string' && number[PhoneNumber._fieldOrder[field]] !== undefined) { 27739 // just concatenate without any formatting 27740 formatted += number[PhoneNumber._fieldOrder[field]]; 27741 if (PhoneNumber._fieldOrder[field] === 'countryCode') { 27742 formatted += ' '; // fix for NOV-107894 27743 } 27744 } 27745 } 27746 } else { 27747 throw e; 27748 } 27749 27750 if (typeof(callback) === 'function') { 27751 callback(formatted); 27752 } 27753 } 27754 return formatted; 27755 }, 27756 27757 /** 27758 * Return an array of names of all available styles that can be used with the current 27759 * formatter. 27760 * @return {Array.<string>} an array of names of styles that are supported by this formatter 27761 */ 27762 getAvailableStyles: function () { 27763 var ret = [], 27764 style; 27765 27766 if (this.fmtdata) { 27767 for (style in this.fmtdata) { 27768 if (this.fmtdata[style].example) { 27769 ret.push(style); 27770 } 27771 } 27772 } 27773 return ret; 27774 }, 27775 27776 /** 27777 * Return an example phone number formatted with the given style. 27778 * 27779 * @param {string|undefined} style style to get an example of, or undefined to use 27780 * the current default style for this formatter 27781 * @return {string|undefined} an example phone number formatted according to the 27782 * given style, or undefined if the style is not recognized or does not have an 27783 * example 27784 */ 27785 getStyleExample: function (style) { 27786 return this.fmtdata[style].example || undefined; 27787 } 27788 }; 27789 27790 27791 /*< PhoneGeoLocator.js */ 27792 /* 27793 * phonegeo.js - Represent a phone number geolocator object. 27794 * 27795 * Copyright © 2014-2015, JEDLSoft 27796 * 27797 * Licensed under the Apache License, Version 2.0 (the "License"); 27798 * you may not use this file except in compliance with the License. 27799 * You may obtain a copy of the License at 27800 * 27801 * http://www.apache.org/licenses/LICENSE-2.0 27802 * 27803 * Unless required by applicable law or agreed to in writing, software 27804 * distributed under the License is distributed on an "AS IS" BASIS, 27805 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27806 * 27807 * See the License for the specific language governing permissions and 27808 * limitations under the License. 27809 */ 27810 27811 /* 27812 !depends 27813 ilib.js 27814 NumberingPlan.js 27815 PhoneLocale.js 27816 PhoneNumber.js 27817 Utils.js 27818 JSUtils.js 27819 ResBundle.js 27820 */ 27821 27822 // !data iddarea area extarea extstates phoneres 27823 27824 27825 27826 /** 27827 * @class 27828 * Create an instance that can geographically locate a phone number.<p> 27829 * 27830 * The location of the number is calculated according to the following rules: 27831 * 27832 * <ol> 27833 * <li>If the areaCode property is undefined or empty, or if the number specifies a 27834 * country code for which we do not have information, then the area property may be 27835 * missing from the returned object. In this case, only the country object will be returned. 27836 * 27837 * <li>If there is no area code, but there is a mobile prefix, service code, or emergency 27838 * code, then a fixed string indicating the type of number will be returned. 27839 * 27840 * <li>The country object is filled out according to the countryCode property of the phone 27841 * number. 27842 * 27843 * <li>If the phone number does not have an explicit country code, the MCC will be used if 27844 * it is available. The country code can be gleaned directly from the MCC. If the MCC 27845 * of the carrier to which the phone is currently connected is available, it should be 27846 * passed in so that local phone numbers will look correct. 27847 * 27848 * <li>If the country's dialling plan mandates a fixed length for phone numbers, and a 27849 * particular number exceeds that length, then the area code will not be given on the 27850 * assumption that the number has problems in the first place and we cannot guess 27851 * correctly. 27852 * </ol> 27853 * 27854 * The returned area property varies in specificity according 27855 * to the locale. In North America, the area is no finer than large parts of states 27856 * or provinces. In Germany and the UK, the area can be as fine as small towns.<p> 27857 * 27858 * If the number passed in is invalid, no geolocation will be performed. If the location 27859 * information about the country where the phone number is located is not available, 27860 * then the area information will be missing and only the country will be available.<p> 27861 * 27862 * The options parameter can contain any one of the following properties: 27863 * 27864 * <ul> 27865 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 27866 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 27867 * but the phone number being geolocated is in Germany, then this class would return the the names 27868 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 27869 * phone number in Munich and return the country "Germany" and the area code "Munich" 27870 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 27871 * If translations are not available, the region and area names are given in English, which should 27872 * always be available. 27873 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 27874 * 27875 * <li><i>onLoad</i> - a callback function to call when the data for the 27876 * locale is fully loaded. When the onLoad option is given, this object 27877 * will attempt to load any missing locale data using the ilib loader callback. 27878 * When the constructor is done (even if the data is already preassembled), the 27879 * onLoad function is called with the current instance as a parameter, so this 27880 * callback can be used with preassembled or dynamic loading or a mix of the two. 27881 * 27882 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27883 * asynchronously. If this option is given as "false", then the "onLoad" 27884 * callback must be given, as the instance returned from this constructor will 27885 * not be usable for a while. 27886 * 27887 * <li><i>loadParams</i> - an object containing parameters to pass to the 27888 * loader callback function when locale data is missing. The parameters are not 27889 * interpretted or modified in any way. They are simply passed along. The object 27890 * may contain any property/value pairs as long as the calling code is in 27891 * agreement with the loader callback function as to what those parameters mean. 27892 * </ul> 27893 * 27894 * @constructor 27895 * @param {Object} options parameters controlling the geolocation of the phone number. 27896 */ 27897 var PhoneGeoLocator = function(options) { 27898 var sync = true, 27899 loadParams = {}, 27900 locale = ilib.getLocale(); 27901 27902 if (options) { 27903 if (options.locale) { 27904 locale = options.locale; 27905 } 27906 27907 if (typeof(options.sync) === 'boolean') { 27908 sync = options.sync; 27909 } 27910 27911 if (options.loadParams) { 27912 loadParams = options.loadParams; 27913 } 27914 } 27915 27916 new PhoneLocale({ 27917 locale: locale, 27918 mcc: options && options.mcc, 27919 countryCode: options && options.countryCode, 27920 sync: sync, 27921 loadParams: loadParams, 27922 onLoad: ilib.bind(this, function (loc) { 27923 this.locale = loc; 27924 new NumberingPlan({ 27925 locale: this.locale, 27926 sync: sync, 27927 loadParams: loadParams, 27928 onLoad: ilib.bind(this, function (plan) { 27929 this.plan = plan; 27930 27931 new ResBundle({ 27932 locale: this.locale, 27933 name: "phoneres", 27934 sync: sync, 27935 loadParams: loadParams, 27936 onLoad: ilib.bind(this, function (rb) { 27937 this.rb = rb; 27938 27939 Utils.loadData({ 27940 name: "iddarea.json", 27941 object: PhoneGeoLocator, 27942 nonlocale: true, 27943 sync: sync, 27944 loadParams: loadParams, 27945 callback: ilib.bind(this, function (data) { 27946 this.regiondata = data; 27947 Utils.loadData({ 27948 name: "area.json", 27949 object: PhoneGeoLocator, 27950 locale: this.locale, 27951 sync: sync, 27952 loadParams: JSUtils.merge(loadParams, { 27953 returnOne: true 27954 }), 27955 callback: ilib.bind(this, function (areadata) { 27956 this.areadata = areadata; 27957 27958 if (options && typeof(options.onLoad) === 'function') { 27959 options.onLoad(this); 27960 } 27961 }) 27962 }); 27963 }) 27964 }); 27965 }) 27966 }); 27967 }) 27968 }); 27969 }) 27970 }); 27971 }; 27972 27973 PhoneGeoLocator.prototype = { 27974 /** 27975 * @private 27976 * 27977 * Used for locales where the area code is very general, and you need to add in 27978 * the initial digits of the subscriber number in order to get the area 27979 * 27980 * @param {string} number 27981 * @param {Object} stateTable 27982 */ 27983 _parseAreaAndSubscriber: function (number, stateTable) { 27984 var ch, 27985 i, 27986 handlerMethod, 27987 newState, 27988 prefix = "", 27989 consumed, 27990 lastLeaf, 27991 currentState, 27992 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 27993 27994 if (!number || !stateTable) { 27995 // can't parse anything 27996 return undefined; 27997 } 27998 27999 //console.log("GeoLocator._parseAreaAndSubscriber: parsing number " + number); 28000 28001 currentState = stateTable; 28002 i = 0; 28003 while (i < number.length) { 28004 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 28005 if (ch >= 0) { 28006 // newState = stateData.states[state][ch]; 28007 newState = currentState.s && currentState.s[ch]; 28008 28009 if (!newState && currentState.s && currentState.s[dot]) { 28010 newState = currentState.s[dot]; 28011 } 28012 28013 if (typeof(newState) === 'object') { 28014 if (typeof(newState.l) !== 'undefined') { 28015 // save for latter if needed 28016 lastLeaf = newState; 28017 consumed = i; 28018 } 28019 // console.info("recognized digit " + ch + " continuing..."); 28020 // recognized digit, so continue parsing 28021 currentState = newState; 28022 i++; 28023 } else { 28024 if (typeof(newState) === 'undefined' || newState === 0) { 28025 // this is possibly a look-ahead and it didn't work... 28026 // so fall back to the last leaf and use that as the 28027 // final state 28028 newState = lastLeaf; 28029 i = consumed; 28030 } 28031 28032 if ((typeof(newState) === 'number' && newState) || 28033 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 28034 // final state 28035 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 28036 handlerMethod = PhoneNumber._states[stateNumber]; 28037 28038 //console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 28039 28040 return (handlerMethod === "area") ? number.substring(0, i+1) : undefined; 28041 } else { 28042 // failed parse. Either no last leaf to fall back to, or there was an explicit 28043 // zero in the table 28044 break; 28045 } 28046 } 28047 } else if (ch === -1) { 28048 // non-transition character, continue parsing in the same state 28049 i++; 28050 } else { 28051 // should not happen 28052 // console.info("skipping character " + ch); 28053 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 28054 i++; 28055 } 28056 } 28057 return undefined; 28058 }, 28059 /** 28060 * @private 28061 * @param prefix 28062 * @param table 28063 * @returns 28064 */ 28065 _matchPrefix: function(prefix, table) { 28066 var i, matchedDot, matchesWithDots = []; 28067 28068 if (table[prefix]) { 28069 return table[prefix]; 28070 } 28071 for (var entry in table) { 28072 if (entry && typeof(entry) === 'string') { 28073 i = 0; 28074 matchedDot = false; 28075 while (i < entry.length && (entry.charAt(i) === prefix.charAt(i) || entry.charAt(i) === '.')) { 28076 if (entry.charAt(i) === '.') { 28077 matchedDot = true; 28078 } 28079 i++; 28080 } 28081 if (i >= entry.length) { 28082 if (matchedDot) { 28083 matchesWithDots.push(entry); 28084 } else { 28085 return table[entry]; 28086 } 28087 } 28088 } 28089 } 28090 28091 // match entries with dots last, so sort the matches so that the entry with the 28092 // most dots sorts last. The entry that ends up at the beginning of the list is 28093 // the best match because it has the fewest dots 28094 if (matchesWithDots.length > 0) { 28095 matchesWithDots.sort(function (left, right) { 28096 return (right < left) ? -1 : ((left < right) ? 1 : 0); 28097 }); 28098 return table[matchesWithDots[0]]; 28099 } 28100 28101 return undefined; 28102 }, 28103 /** 28104 * @private 28105 * @param number 28106 * @param data 28107 * @param locale 28108 * @param plan 28109 * @param options 28110 * @returns {Object} 28111 */ 28112 _getAreaInfo: function(number, data, locale, plan, options) { 28113 var sync = true, 28114 ret = {}, 28115 countryCode, 28116 areaInfo, 28117 temp, 28118 areaCode, 28119 geoTable, 28120 tempNumber, 28121 prefix; 28122 28123 if (options && typeof(options.sync) === 'boolean') { 28124 sync = options.sync; 28125 } 28126 28127 prefix = number.areaCode || number.serviceCode; 28128 geoTable = data; 28129 28130 if (prefix !== undefined) { 28131 if (plan.getExtendedAreaCode()) { 28132 // for countries where the area code is very general and large, and you need a few initial 28133 // digits of the subscriber number in order find the actual area 28134 tempNumber = prefix + number.subscriberNumber; 28135 tempNumber = tempNumber.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 28136 28137 Utils.loadData({ 28138 name: "extarea.json", 28139 object: PhoneGeoLocator, 28140 locale: locale, 28141 sync: sync, 28142 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 28143 callback: ilib.bind(this, function (data) { 28144 this.extarea = data; 28145 Utils.loadData({ 28146 name: "extstates.json", 28147 object: PhoneGeoLocator, 28148 locale: locale, 28149 sync: sync, 28150 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 28151 callback: ilib.bind(this, function (data) { 28152 this.extstates = data; 28153 geoTable = this.extarea; 28154 if (this.extarea && this.extstates) { 28155 prefix = this._parseAreaAndSubscriber(tempNumber, this.extstates); 28156 } 28157 28158 if (!prefix) { 28159 // not a recognized prefix, so now try the general table 28160 geoTable = this.areadata; 28161 prefix = number.areaCode || number.serviceCode; 28162 } 28163 28164 if ((!plan.fieldLengths || 28165 plan.getFieldLength('maxLocalLength') === undefined || 28166 !number.subscriberNumber || 28167 number.subscriberNumber.length <= plan.fieldLengths('maxLocalLength'))) { 28168 areaInfo = this._matchPrefix(prefix, geoTable); 28169 if (areaInfo && areaInfo.sn && areaInfo.ln) { 28170 //console.log("Found areaInfo " + JSON.stringify(areaInfo)); 28171 ret.area = { 28172 sn: this.rb.getString(areaInfo.sn).toString(), 28173 ln: this.rb.getString(areaInfo.ln).toString() 28174 }; 28175 } 28176 } 28177 }) 28178 }); 28179 }) 28180 }); 28181 28182 } else if (!plan || 28183 plan.getFieldLength('maxLocalLength') === undefined || 28184 !number.subscriberNumber || 28185 number.subscriberNumber.length <= plan.getFieldLength('maxLocalLength')) { 28186 if (geoTable) { 28187 areaCode = prefix.replace(/[wWpPtT\+#\*]/g, ''); 28188 areaInfo = this._matchPrefix(areaCode, geoTable); 28189 28190 if (areaInfo && areaInfo.sn && areaInfo.ln) { 28191 ret.area = { 28192 sn: this.rb.getString(areaInfo.sn).toString(), 28193 ln: this.rb.getString(areaInfo.ln).toString() 28194 }; 28195 } else if (number.serviceCode) { 28196 ret.area = { 28197 sn: this.rb.getString("Service Number").toString(), 28198 ln: this.rb.getString("Service Number").toString() 28199 }; 28200 } 28201 } else { 28202 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 28203 if (countryCode !== "0" && this.regiondata) { 28204 temp = this.regiondata[countryCode]; 28205 if (temp && temp.sn) { 28206 ret.country = { 28207 sn: this.rb.getString(temp.sn).toString(), 28208 ln: this.rb.getString(temp.ln).toString(), 28209 code: this.locale.getRegion() 28210 }; 28211 } 28212 } 28213 } 28214 } else { 28215 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 28216 if (countryCode !== "0" && this.regiondata) { 28217 temp = this.regiondata[countryCode]; 28218 if (temp && temp.sn) { 28219 ret.country = { 28220 sn: this.rb.getString(temp.sn).toString(), 28221 ln: this.rb.getString(temp.ln).toString(), 28222 code: this.locale.getRegion() 28223 }; 28224 } 28225 } 28226 } 28227 28228 } else if (number.mobilePrefix) { 28229 ret.area = { 28230 sn: this.rb.getString("Mobile Number").toString(), 28231 ln: this.rb.getString("Mobile Number").toString() 28232 }; 28233 } else if (number.emergency) { 28234 ret.area = { 28235 sn: this.rb.getString("Emergency Services Number").toString(), 28236 ln: this.rb.getString("Emergency Services Number").toString() 28237 }; 28238 } 28239 28240 return ret; 28241 }, 28242 /** 28243 * Returns a the location of the given phone number, if known. 28244 * The returned object has 2 properties, each of which has an sn (short name) 28245 * and an ln (long name) string. Additionally, the country code, if given, 28246 * includes the 2 letter ISO code for the recognized country. 28247 * { 28248 * "country": { 28249 * "sn": "North America", 28250 * "ln": "North America and the Caribbean Islands", 28251 * "code": "us" 28252 * }, 28253 * "area": { 28254 * "sn": "California", 28255 * "ln": "Central California: San Jose, Los Gatos, Milpitas, Sunnyvale, Cupertino, Gilroy" 28256 * } 28257 * } 28258 * 28259 * The location name is subject to the following rules: 28260 * 28261 * If the areaCode property is undefined or empty, or if the number specifies a 28262 * country code for which we do not have information, then the area property may be 28263 * missing from the returned object. In this case, only the country object will be returned. 28264 * 28265 * If there is no area code, but there is a mobile prefix, service code, or emergency 28266 * code, then a fixed string indicating the type of number will be returned. 28267 * 28268 * The country object is filled out according to the countryCode property of the phone 28269 * number. 28270 * 28271 * If the phone number does not have an explicit country code, the MCC will be used if 28272 * it is available. The country code can be gleaned directly from the MCC. If the MCC 28273 * of the carrier to which the phone is currently connected is available, it should be 28274 * passed in so that local phone numbers will look correct. 28275 * 28276 * If the country's dialling plan mandates a fixed length for phone numbers, and a 28277 * particular number exceeds that length, then the area code will not be given on the 28278 * assumption that the number has problems in the first place and we cannot guess 28279 * correctly. 28280 * 28281 * The returned area property varies in specificity according 28282 * to the locale. In North America, the area is no finer than large parts of states 28283 * or provinces. In Germany and the UK, the area can be as fine as small towns. 28284 * 28285 * The strings returned from this function are already localized to the 28286 * given locale, and thus are ready for display to the user. 28287 * 28288 * If the number passed in is invalid, an empty object is returned. If the location 28289 * information about the country where the phone number is located is not available, 28290 * then the area information will be missing and only the country will be returned. 28291 * 28292 * The options parameter can contain any one of the following properties: 28293 * 28294 * <ul> 28295 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 28296 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 28297 * but the phone number being geolocated is in Germany, then this class would return the the names 28298 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 28299 * phone number in Munich and return the country "Germany" and the area code "Munich" 28300 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 28301 * If translations are not available, the region and area names are given in English, which should 28302 * always be available. 28303 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 28304 * 28305 * <li><i>onLoad</i> - a callback function to call when the data for the 28306 * locale is fully loaded. When the onLoad option is given, this object 28307 * will attempt to load any missing locale data using the ilib loader callback. 28308 * When the constructor is done (even if the data is already preassembled), the 28309 * onLoad function is called with the current instance as a parameter, so this 28310 * callback can be used with preassembled or dynamic loading or a mix of the two. 28311 * 28312 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 28313 * asynchronously. If this option is given as "false", then the "onLoad" 28314 * callback must be given, as the instance returned from this constructor will 28315 * not be usable for a while. 28316 * 28317 * <li><i>loadParams</i> - an object containing parameters to pass to the 28318 * loader callback function when locale data is missing. The parameters are not 28319 * interpretted or modified in any way. They are simply passed along. The object 28320 * may contain any property/value pairs as long as the calling code is in 28321 * agreement with the loader callback function as to what those parameters mean. 28322 * </ul> 28323 * 28324 * @param {PhoneNumber} number phone number to locate 28325 * @param {Object} options options governing the way this ares is loaded 28326 * @return {Object} an object 28327 * that describes the country and the area in that country corresponding to this 28328 * phone number. Each of the country and area contain a short name (sn) and long 28329 * name (ln) that describes the location. 28330 */ 28331 locate: function(number, options) { 28332 var loadParams = {}, 28333 ret = {}, 28334 region, 28335 countryCode, 28336 temp, 28337 plan, 28338 areaResult, 28339 phoneLoc = this.locale, 28340 sync = true; 28341 28342 if (number === undefined || typeof(number) !== 'object' || !(number instanceof PhoneNumber)) { 28343 return ret; 28344 } 28345 28346 if (options) { 28347 if (typeof(options.sync) !== 'undefined') { 28348 sync = (options.sync == true); 28349 } 28350 28351 if (options.loadParams) { 28352 loadParams = options.loadParams; 28353 } 28354 } 28355 28356 // console.log("GeoLocator.locate: looking for geo for number " + JSON.stringify(number)); 28357 region = this.locale.getRegion(); 28358 if (number.countryCode !== undefined && this.regiondata) { 28359 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); 28360 temp = this.regiondata[countryCode]; 28361 phoneLoc = number.destinationLocale; 28362 plan = number.destinationPlan; 28363 ret.country = { 28364 sn: this.rb.getString(temp.sn).toString(), 28365 ln: this.rb.getString(temp.ln).toString(), 28366 code: phoneLoc.getRegion() 28367 }; 28368 } 28369 28370 if (!plan) { 28371 plan = this.plan; 28372 } 28373 28374 Utils.loadData({ 28375 name: "area.json", 28376 object: PhoneGeoLocator, 28377 locale: phoneLoc, 28378 sync: sync, 28379 loadParams: JSUtils.merge(loadParams, { 28380 returnOne: true 28381 }), 28382 callback: ilib.bind(this, function (areadata) { 28383 if (areadata) { 28384 this.areadata = areadata; 28385 } 28386 areaResult = this._getAreaInfo(number, this.areadata, phoneLoc, plan, options); 28387 ret = JSUtils.merge(ret, areaResult); 28388 28389 if (ret.country === undefined) { 28390 countryCode = number.locale._mapRegiontoCC(region); 28391 28392 if (countryCode !== "0" && this.regiondata) { 28393 temp = this.regiondata[countryCode]; 28394 if (temp && temp.sn) { 28395 ret.country = { 28396 sn: this.rb.getString(temp.sn).toString(), 28397 ln: this.rb.getString(temp.ln).toString(), 28398 code: this.locale.getRegion() 28399 }; 28400 } 28401 } 28402 } 28403 }) 28404 }); 28405 28406 return ret; 28407 }, 28408 28409 /** 28410 * Returns a string that describes the ISO-3166-2 country code of the given phone 28411 * number.<p> 28412 * 28413 * If the phone number is a local phone number and does not contain 28414 * any country information, this routine will return the region for the current 28415 * formatter instance. 28416 * 28417 * @param {PhoneNumber} number An PhoneNumber instance 28418 * @return {string} 28419 */ 28420 country: function(number) { 28421 var countryCode, 28422 region, 28423 phoneLoc; 28424 28425 if (!number || !(number instanceof PhoneNumber)) { 28426 return ""; 28427 } 28428 28429 phoneLoc = number.locale; 28430 28431 region = (number.countryCode && phoneLoc._mapCCtoRegion(number.countryCode)) || 28432 (number.locale && number.locale.region) || 28433 phoneLoc.locale.getRegion() || 28434 this.locale.getRegion(); 28435 28436 countryCode = number.countryCode || phoneLoc._mapRegiontoCC(region); 28437 28438 if (number.areaCode) { 28439 region = phoneLoc._mapAreatoRegion(countryCode, number.areaCode); 28440 } else if (countryCode === "33" && number.serviceCode) { 28441 // french departments are in the service code, not the area code 28442 region = phoneLoc._mapAreatoRegion(countryCode, number.serviceCode); 28443 } 28444 return region; 28445 } 28446 }; 28447 28448 28449 /*< Measurement.js */ 28450 /* 28451 * Measurement.js - Measurement unit superclass 28452 * 28453 * Copyright © 2014-2015, JEDLSoft 28454 * 28455 * Licensed under the Apache License, Version 2.0 (the "License"); 28456 * you may not use this file except in compliance with the License. 28457 * You may obtain a copy of the License at 28458 * 28459 * http://www.apache.org/licenses/LICENSE-2.0 28460 * 28461 * Unless required by applicable law or agreed to in writing, software 28462 * distributed under the License is distributed on an "AS IS" BASIS, 28463 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28464 * 28465 * See the License for the specific language governing permissions and 28466 * limitations under the License. 28467 */ 28468 28469 /** 28470 * @class 28471 * Superclass for measurement instances that contains shared functionality 28472 * and defines the interface. <p> 28473 * 28474 * This class is never instantiated on its own. Instead, measurements should 28475 * be created using the {@link MeasurementFactory} function, which creates the 28476 * correct subclass based on the given parameters.<p> 28477 * 28478 * @private 28479 * @constructor 28480 */ 28481 var Measurement = function() { 28482 }; 28483 28484 /** 28485 * @private 28486 */ 28487 Measurement._constructors = {}; 28488 28489 Measurement.prototype = { 28490 /** 28491 * Return the normalized name of the given units. If the units are 28492 * not recognized, this method returns its parameter unmodified.<p> 28493 * 28494 * Examples: 28495 * 28496 * <ui> 28497 * <li>"metres" gets normalized to "meter"<br> 28498 * <li>"ml" gets normalized to "milliliter"<br> 28499 * <li>"foobar" gets normalized to "foobar" (no change because it is not recognized) 28500 * </ul> 28501 * 28502 * @param {string} name name of the units to normalize. 28503 * @returns {string} normalized name of the units 28504 */ 28505 normalizeUnits: function(name) { 28506 return this.aliases[name] || name; 28507 }, 28508 28509 /** 28510 * Return the normalized units used in this measurement. 28511 * @return {string} name of the unit of measurement 28512 */ 28513 getUnit: function() { 28514 return this.unit; 28515 }, 28516 28517 /** 28518 * Return the units originally used to construct this measurement 28519 * before it was normalized. 28520 * @return {string} name of the unit of measurement 28521 */ 28522 getOriginalUnit: function() { 28523 return this.originalUnit; 28524 }, 28525 28526 /** 28527 * Return the numeric amount of this measurement. 28528 * @return {number} the numeric amount of this measurement 28529 */ 28530 getAmount: function() { 28531 return this.amount; 28532 }, 28533 28534 /** 28535 * Return the type of this measurement. Examples are "mass", 28536 * "length", "speed", etc. Measurements can only be converted 28537 * to measurements of the same type.<p> 28538 * 28539 * The type of the units is determined automatically from the 28540 * units. For example, the unit "grams" is type "mass". Use the 28541 * static call {@link Measurement.getAvailableUnits} 28542 * to find out what units this version of ilib supports. 28543 * 28544 * @return {string} the name of the type of this measurement 28545 */ 28546 getMeasure: function() {}, 28547 28548 /** 28549 * Return a new measurement instance that is converted to a new 28550 * measurement unit. Measurements can only be converted 28551 * to measurements of the same type.<p> 28552 * 28553 * @param {string} to The name of the units to convert to 28554 * @return {Measurement|undefined} the converted measurement 28555 * or undefined if the requested units are for a different 28556 * measurement type 28557 */ 28558 convert: function(to) {}, 28559 28560 /** 28561 * Scale the measurement unit to an acceptable level. The scaling 28562 * happens so that the integer part of the amount is as small as 28563 * possible without being below zero. This will result in the 28564 * largest units that can represent this measurement without 28565 * fractions. Measurements can only be scaled to other measurements 28566 * of the same type. 28567 * 28568 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28569 * or undefined if the system can be inferred from the current measure 28570 * @return {Measurement} a new instance that is scaled to the 28571 * right level 28572 */ 28573 scale: function(measurementsystem) {}, 28574 28575 /** 28576 * Localize the measurement to the commonly used measurement in that locale, for example 28577 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28578 * the formatted number should be automatically converted to the most appropriate 28579 * measure in the other system, in this case, mph. The formatted result should 28580 * appear as "37.3 mph". 28581 * 28582 * @param {string} locale current locale string 28583 * @returns {Measurement} a new instance that is converted to locale 28584 */ 28585 localize: function(locale) {} 28586 }; 28587 28588 28589 28590 /*< UnknownUnit.js */ 28591 /* 28592 * Unknown.js - Dummy unit conversions for unknown types 28593 * 28594 * Copyright © 2014-2015, JEDLSoft 28595 * 28596 * Licensed under the Apache License, Version 2.0 (the "License"); 28597 * you may not use this file except in compliance with the License. 28598 * You may obtain a copy of the License at 28599 * 28600 * http://www.apache.org/licenses/LICENSE-2.0 28601 * 28602 * Unless required by applicable law or agreed to in writing, software 28603 * distributed under the License is distributed on an "AS IS" BASIS, 28604 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28605 * 28606 * See the License for the specific language governing permissions and 28607 * limitations under the License. 28608 */ 28609 28610 // !depends Measurement.js 28611 28612 28613 /** 28614 * @class 28615 * Create a new unknown measurement instance. 28616 * 28617 * @constructor 28618 * @extends Measurement 28619 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28620 * the construction of this instance 28621 */ 28622 var UnknownUnit = function (options) { 28623 if (options) { 28624 this.unit = options.unit; 28625 this.amount = options.amount; 28626 } 28627 }; 28628 28629 UnknownUnit.prototype = new Measurement(); 28630 UnknownUnit.prototype.parent = Measurement; 28631 UnknownUnit.prototype.constructor = UnknownUnit; 28632 28633 UnknownUnit.aliases = { 28634 "unknown":"unknown" 28635 }; 28636 28637 /** 28638 * Return the type of this measurement. Examples are "mass", 28639 * "length", "speed", etc. Measurements can only be converted 28640 * to measurements of the same type.<p> 28641 * 28642 * The type of the units is determined automatically from the 28643 * units. For example, the unit "grams" is type "mass". Use the 28644 * static call {@link Measurement.getAvailableUnits} 28645 * to find out what units this version of ilib supports. 28646 * 28647 * @return {string} the name of the type of this measurement 28648 */ 28649 UnknownUnit.prototype.getMeasure = function() { 28650 return "unknown"; 28651 }; 28652 28653 /** 28654 * Return a new measurement instance that is converted to a new 28655 * measurement unit. Measurements can only be converted 28656 * to measurements of the same type.<p> 28657 * 28658 * @param {string} to The name of the units to convert to 28659 * @return {Measurement|undefined} the converted measurement 28660 * or undefined if the requested units are for a different 28661 * measurement type 28662 */ 28663 UnknownUnit.prototype.convert = function(to) { 28664 return undefined; 28665 }; 28666 28667 /** 28668 * Convert a unknown to another measure. 28669 * @static 28670 * @param {string} to unit to convert to 28671 * @param {string} from unit to convert from 28672 * @param {number} unknown amount to be convert 28673 * @returns {number|undefined} the converted amount 28674 */ 28675 UnknownUnit.convert = function(to, from, unknown) { 28676 return undefined; 28677 }; 28678 28679 /** 28680 * Localize the measurement to the commonly used measurement in that locale. For example 28681 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28682 * the formatted number should be automatically converted to the most appropriate 28683 * measure in the other system, in this case, mph. The formatted result should 28684 * appear as "37.3 mph". 28685 * 28686 * @param {string} locale current locale string 28687 * @returns {Measurement} a new instance that is converted to locale 28688 */ 28689 UnknownUnit.prototype.localize = function(locale) { 28690 return new UnknownUnit({ 28691 unit: this.unit, 28692 amount: this.amount 28693 }); 28694 }; 28695 28696 /** 28697 * Scale the measurement unit to an acceptable level. The scaling 28698 * happens so that the integer part of the amount is as small as 28699 * possible without being below zero. This will result in the 28700 * largest units that can represent this measurement without 28701 * fractions. Measurements can only be scaled to other measurements 28702 * of the same type. 28703 * 28704 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28705 * or undefined if the system can be inferred from the current measure 28706 * @return {Measurement} a new instance that is scaled to the 28707 * right level 28708 */ 28709 UnknownUnit.prototype.scale = function(measurementsystem) { 28710 return new UnknownUnit({ 28711 unit: this.unit, 28712 amount: this.amount 28713 }); 28714 }; 28715 28716 /** 28717 * @private 28718 * @static 28719 */ 28720 UnknownUnit.getMeasures = function () { 28721 return []; 28722 }; 28723 28724 28725 /*< AreaUnit.js */ 28726 /* 28727 * area.js - Unit conversions for Area 28728 * 28729 * Copyright © 2014-2015, JEDLSoft 28730 * 28731 * Licensed under the Apache License, Version 2.0 (the "License"); 28732 * you may not use this file except in compliance with the License. 28733 * You may obtain a copy of the License at 28734 * 28735 * http://www.apache.org/licenses/LICENSE-2.0 28736 * 28737 * Unless required by applicable law or agreed to in writing, software 28738 * distributed under the License is distributed on an "AS IS" BASIS, 28739 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28740 * 28741 * See the License for the specific language governing permissions and 28742 * limitations under the License. 28743 */ 28744 28745 /* 28746 !depends 28747 Measurement.js 28748 */ 28749 28750 28751 /** 28752 * @class 28753 * Create a new area measurement instance. 28754 * @constructor 28755 * @extends Measurement 28756 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28757 * the construction of this instance 28758 */ 28759 var AreaUnit = function (options) { 28760 this.unit = "square meter"; 28761 this.amount = 0; 28762 this.aliases = AreaUnit.aliases; // share this table in all instances 28763 28764 if (options) { 28765 if (typeof(options.unit) !== 'undefined') { 28766 this.originalUnit = options.unit; 28767 this.unit = this.aliases[options.unit] || options.unit; 28768 } 28769 28770 if (typeof(options.amount) === 'object') { 28771 if (options.amount.getMeasure() === "area") { 28772 this.amount = AreaUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 28773 } else { 28774 throw "Cannot convert unit " + options.amount.unit + " to area"; 28775 } 28776 } else if (typeof(options.amount) !== 'undefined') { 28777 this.amount = parseFloat(options.amount); 28778 } 28779 } 28780 28781 if (typeof(AreaUnit.ratios[this.unit]) === 'undefined') { 28782 throw "Unknown unit: " + options.unit; 28783 } 28784 }; 28785 28786 AreaUnit.prototype = new Measurement(); 28787 AreaUnit.prototype.parent = Measurement; 28788 AreaUnit.prototype.constructor = AreaUnit; 28789 28790 AreaUnit.ratios = { 28791 /* index square cm, square meter, hectare, square km, , square inch square foot, square yard, acre, square mile */ 28792 "square centimeter":[1, 1, 0.0001, 1e-8, 1e-10, 0.15500031, 0.00107639104, 0.000119599005, 2.47105381e-8, 3.86102159e-11 ], 28793 "square meter": [2, 10000, 1, 1e-4, 1e-6, 1550, 10.7639, 1.19599, 0.000247105, 3.861e-7 ], 28794 "hectare": [3, 100000000, 10000, 1, 0.01, 1.55e+7, 107639, 11959.9, 2.47105 , 0.00386102 ], 28795 "square km": [4, 10000000000, 1e+6, 100, 1, 1.55e+9, 1.076e+7, 1.196e+6, 247.105 , 0.386102 ], 28796 "square inch": [5, 6.4516, 0.00064516, 6.4516e-8, 6.4516e-10, 1, 0.000771605, 0.0007716051, 1.5942e-7, 2.491e-10 ], 28797 "square foot": [6, 929.0304, 0.092903, 9.2903e-6, 9.2903e-8, 144, 1, 0.111111, 2.2957e-5, 3.587e-8 ], 28798 "square yard": [7, 8361.2736, 0.836127, 8.3613e-5, 8.3613e-7, 1296, 9, 1, 0.000206612, 3.2283e-7 ], 28799 "acre": [8, 40468564.2, 4046.86, 0.404686, 0.00404686, 6.273e+6, 43560, 4840, 1, 0.0015625 ], 28800 "square mile": [9, 2.58998811e+10, 2.59e+6, 258.999, 2.58999, 4.014e+9, 2.788e+7, 3.098e+6, 640, 1 ] 28801 } 28802 28803 /** 28804 * Return the type of this measurement. Examples are "mass", 28805 * "length", "speed", etc. Measurements can only be converted 28806 * to measurements of the same type.<p> 28807 * 28808 * The type of the units is determined automatically from the 28809 * units. For example, the unit "grams" is type "mass". Use the 28810 * static call {@link Measurement.getAvailableUnits} 28811 * to find out what units this version of ilib supports. 28812 * 28813 * @return {string} the name of the type of this measurement 28814 */ 28815 AreaUnit.prototype.getMeasure = function() { 28816 return "area"; 28817 }; 28818 28819 /** 28820 * Return a new measurement instance that is converted to a new 28821 * measurement unit. Measurements can only be converted 28822 * to measurements of the same type.<p> 28823 * 28824 * @param {string} to The name of the units to convert to 28825 * @return {Measurement|undefined} the converted measurement 28826 * or undefined if the requested units are for a different 28827 * measurement type 28828 * 28829 */ 28830 AreaUnit.prototype.convert = function(to) { 28831 if (!to || typeof(AreaUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 28832 return undefined; 28833 } 28834 return new AreaUnit({ 28835 unit: to, 28836 amount: this 28837 }); 28838 }; 28839 28840 AreaUnit.aliases = { 28841 "square centimeter":"square centimeter", 28842 "square cm":"square centimeter", 28843 "sq cm":"square centimeter", 28844 "Square Cm":"square centimeter", 28845 "square Centimeters":"square centimeter", 28846 "square Centimeter":"square centimeter", 28847 "square Centimetre":"square centimeter", 28848 "square Centimetres":"square centimeter", 28849 "square centimeters":"square centimeter", 28850 "Square km": "square km", 28851 "Square kilometre":"square km", 28852 "square kilometer":"square km", 28853 "square kilometre":"square km", 28854 "square kilometers":"square km", 28855 "square kilometres":"square km", 28856 "square km":"square km", 28857 "sq km":"square km", 28858 "km2":"square km", 28859 "Hectare":"hectare", 28860 "hectare":"hectare", 28861 "ha":"hectare", 28862 "Square meter": "square meter", 28863 "Square meters":"square meter", 28864 "square meter": "square meter", 28865 "square meters":"square meter", 28866 "Square metre": "square meter", 28867 "Square metres":"square meter", 28868 "square metres": "square meter", 28869 "Square Metres":"square meter", 28870 "sqm":"square meter", 28871 "m2": "square meter", 28872 "Square mile":"square mile", 28873 "Square miles":"square mile", 28874 "square mile":"square mile", 28875 "square miles":"square mile", 28876 "square mi":"square mile", 28877 "Square mi":"square mile", 28878 "sq mi":"square mile", 28879 "mi2":"square mile", 28880 "Acre": "acre", 28881 "acre": "acre", 28882 "Acres":"acre", 28883 "acres":"acre", 28884 "Square yard": "square yard", 28885 "Square yards":"square yard", 28886 "square yard": "square yard", 28887 "square yards":"square yard", 28888 "yd2":"square yard", 28889 "Square foot": "square foot", 28890 "square foot": "square foot", 28891 "Square feet": "square foot", 28892 "Square Feet": "square foot", 28893 "sq ft":"square foot", 28894 "ft2":"square foot", 28895 "Square inch":"square inch", 28896 "square inch":"square inch", 28897 "Square inches":"square inch", 28898 "square inches":"square inch", 28899 "in2":"square inch" 28900 }; 28901 28902 /** 28903 * Convert a Area to another measure. 28904 * @static 28905 * @param to {string} unit to convert to 28906 * @param from {string} unit to convert from 28907 * @param area {number} amount to be convert 28908 * @returns {number|undefined} the converted amount 28909 */ 28910 AreaUnit.convert = function(to, from, area) { 28911 from = AreaUnit.aliases[from] || from; 28912 to = AreaUnit.aliases[to] || to; 28913 var fromRow = AreaUnit.ratios[from]; 28914 var toRow = AreaUnit.ratios[to]; 28915 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 28916 return undefined; 28917 } 28918 return area* fromRow[toRow[0]]; 28919 }; 28920 28921 /** 28922 * @private 28923 * @static 28924 */ 28925 AreaUnit.getMeasures = function () { 28926 var ret = []; 28927 for (var m in AreaUnit.ratios) { 28928 ret.push(m); 28929 } 28930 return ret; 28931 }; 28932 28933 AreaUnit.metricSystem = { 28934 "square centimeter" : 1, 28935 "square meter" : 2, 28936 "hectare" : 3, 28937 "square km" : 4 28938 }; 28939 AreaUnit.imperialSystem = { 28940 "square inch" : 5, 28941 "square foot" : 6, 28942 "square yard" : 7, 28943 "acre" : 8, 28944 "square mile" : 9 28945 }; 28946 AreaUnit.uscustomarySystem = { 28947 "square inch" : 5, 28948 "square foot" : 6, 28949 "square yard" : 7, 28950 "acre" : 8, 28951 "square mile" : 9 28952 }; 28953 28954 AreaUnit.metricToUScustomary = { 28955 "square centimeter" : "square inch", 28956 "square meter" : "square yard", 28957 "hectare" : "acre", 28958 "square km" : "square mile" 28959 }; 28960 AreaUnit.usCustomaryToMetric = { 28961 "square inch" : "square centimeter", 28962 "square foot" : "square meter", 28963 "square yard" : "square meter", 28964 "acre" : "hectare", 28965 "square mile" : "square km" 28966 }; 28967 28968 28969 /** 28970 * Scale the measurement unit to an acceptable level. The scaling 28971 * happens so that the integer part of the amount is as small as 28972 * possible without being below zero. This will result in the 28973 * largest units that can represent this measurement without 28974 * fractions. Measurements can only be scaled to other measurements 28975 * of the same type. 28976 * 28977 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28978 * or undefined if the system can be inferred from the current measure 28979 * @return {Measurement} a new instance that is scaled to the 28980 * right level 28981 */ 28982 AreaUnit.prototype.scale = function(measurementsystem) { 28983 var fromRow = AreaUnit.ratios[this.unit]; 28984 var mSystem; 28985 28986 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 28987 && typeof(AreaUnit.metricSystem[this.unit]) !== 'undefined')) { 28988 mSystem = AreaUnit.metricSystem; 28989 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 28990 && typeof(AreaUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 28991 mSystem = AreaUnit.uscustomarySystem; 28992 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 28993 && typeof(AreaUnit.imperialSystem[this.unit]) !== 'undefined')) { 28994 mSystem = AreaUnit.imperialSystem; 28995 } 28996 28997 var area = this.amount; 28998 var munit = this.unit; 28999 29000 area = 18446744073709551999; 29001 29002 for (var m in mSystem) { 29003 var tmp = this.amount * fromRow[mSystem[m]]; 29004 if (tmp >= 1 && tmp < area) { 29005 area = tmp; 29006 munit = m; 29007 } 29008 } 29009 29010 return new AreaUnit({ 29011 unit: munit, 29012 amount: area 29013 }); 29014 }; 29015 29016 /** 29017 * Localize the measurement to the commonly used measurement in that locale. For example 29018 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29019 * the formatted number should be automatically converted to the most appropriate 29020 * measure in the other system, in this case, mph. The formatted result should 29021 * appear as "37.3 mph". 29022 * 29023 * @param {string} locale current locale string 29024 * @returns {Measurement} a new instance that is converted to locale 29025 */ 29026 AreaUnit.prototype.localize = function(locale) { 29027 var to; 29028 if (locale === "en-US" || locale === "en-GB") { 29029 to = AreaUnit.metricToUScustomary[this.unit] || this.unit; 29030 } else { 29031 to = AreaUnit.usCustomaryToMetric[this.unit] || this.unit; 29032 } 29033 return new AreaUnit({ 29034 unit: to, 29035 amount: this 29036 }); 29037 }; 29038 29039 29040 //register with the factory method 29041 Measurement._constructors["area"] = AreaUnit; 29042 29043 29044 /*< DigitalStorageUnit.js */ 29045 /* 29046 * digitalStorage.js - Unit conversions for Digital Storage 29047 * 29048 * Copyright © 2014-2015, JEDLSoft 29049 * 29050 * Licensed under the Apache License, Version 2.0 (the "License"); 29051 * you may not use this file except in compliance with the License. 29052 * You may obtain a copy of the License at 29053 * 29054 * http://www.apache.org/licenses/LICENSE-2.0 29055 * 29056 * Unless required by applicable law or agreed to in writing, software 29057 * distributed under the License is distributed on an "AS IS" BASIS, 29058 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29059 * 29060 * See the License for the specific language governing permissions and 29061 * limitations under the License. 29062 */ 29063 29064 /* 29065 !depends 29066 Measurement.js 29067 */ 29068 29069 29070 /** 29071 * @class 29072 * Create a new DigitalStorage measurement instance. 29073 * 29074 * @constructor 29075 * @extends Measurement 29076 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29077 * the construction of this instance 29078 */ 29079 var DigitalStorageUnit = function (options) { 29080 this.unit = "byte"; 29081 this.amount = 0; 29082 this.aliases = DigitalStorageUnit.aliases; // share this table in all instances 29083 29084 if (options) { 29085 if (typeof(options.unit) !== 'undefined') { 29086 this.originalUnit = options.unit; 29087 this.unit = this.aliases[options.unit] || options.unit; 29088 } 29089 29090 if (typeof(options.amount) === 'object') { 29091 if (options.amount.getMeasure() === "digitalStorage") { 29092 this.amount = DigitalStorageUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29093 } else { 29094 throw "Cannot convert unit " + options.amount.unit + " to a digitalStorage"; 29095 } 29096 } else if (typeof(options.amount) !== 'undefined') { 29097 this.amount = parseFloat(options.amount); 29098 } 29099 } 29100 29101 if (typeof(DigitalStorageUnit.ratios[this.unit]) === 'undefined') { 29102 throw "Unknown unit: " + options.unit; 29103 } 29104 }; 29105 29106 DigitalStorageUnit.prototype = new Measurement(); 29107 DigitalStorageUnit.prototype.parent = Measurement; 29108 DigitalStorageUnit.prototype.constructor = DigitalStorageUnit; 29109 29110 DigitalStorageUnit.ratios = { 29111 /* # bit byte kb kB mb mB gb gB tb tB pb pB */ 29112 "bit": [ 1, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 8.881784197e-16, 1.110223025e-16 ], 29113 "byte": [ 2, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13, 7.105427358e-15, 8.881784197e-16 ], 29114 "kilobit": [ 3, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13 ], 29115 "kilobyte": [ 4, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13 ], 29116 "megabit": [ 5, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10 ], 29117 "megabyte": [ 6, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10 ], 29118 "gigabit": [ 7, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7 ], 29119 "gigabyte": [ 8, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7 ], 29120 "terabit": [ 9, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4 ], 29121 "terabyte": [ 10, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625 ], 29122 "petabit": [ 11, 1.125899907e15, 1.407374884e14, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125 ], 29123 "petabyte": [ 12, 9.007199255e15, 1.125899907e15, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1 ] 29124 }; 29125 29126 DigitalStorageUnit.bitSystem = { 29127 "bit": 1, 29128 "kilobit": 3, 29129 "megabit": 5, 29130 "gigabit": 7, 29131 "terabit": 9, 29132 "petabit": 11 29133 }; 29134 DigitalStorageUnit.byteSystem = { 29135 "byte": 2, 29136 "kilobyte": 4, 29137 "megabyte": 6, 29138 "gigabyte": 8, 29139 "terabyte": 10, 29140 "petabyte": 12 29141 }; 29142 29143 /** 29144 * Return the type of this measurement. Examples are "mass", 29145 * "length", "speed", etc. Measurements can only be converted 29146 * to measurements of the same type.<p> 29147 * 29148 * The type of the units is determined automatically from the 29149 * units. For example, the unit "grams" is type "mass". Use the 29150 * static call {@link Measurement.getAvailableUnits} 29151 * to find out what units this version of ilib supports. 29152 * 29153 * @return {string} the name of the type of this measurement 29154 */ 29155 DigitalStorageUnit.prototype.getMeasure = function() { 29156 return "digitalStorage"; 29157 }; 29158 29159 /** 29160 * Return a new measurement instance that is converted to a new 29161 * measurement unit. Measurements can only be converted 29162 * to measurements of the same type.<p> 29163 * 29164 * @param {string} to The name of the units to convert to 29165 * @return {Measurement|undefined} the converted measurement 29166 * or undefined if the requested units are for a different 29167 * measurement type 29168 * 29169 */ 29170 DigitalStorageUnit.prototype.convert = function(to) { 29171 if (!to || typeof(DigitalStorageUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29172 return undefined; 29173 } 29174 return new DigitalStorageUnit({ 29175 unit: to, 29176 amount: this 29177 }); 29178 }; 29179 29180 /** 29181 * Localize the measurement to the commonly used measurement in that locale. For example 29182 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29183 * the formatted number should be automatically converted to the most appropriate 29184 * measure in the other system, in this case, mph. The formatted result should 29185 * appear as "37.3 mph". 29186 * 29187 * @param {string} locale current locale string 29188 * @returns {Measurement} a new instance that is converted to locale 29189 */ 29190 DigitalStorageUnit.prototype.localize = function(locale) { 29191 return new DigitalStorageUnit({ 29192 unit: this.unit, 29193 amount: this.amount 29194 }); 29195 }; 29196 29197 /** 29198 * Scale the measurement unit to an acceptable level. The scaling 29199 * happens so that the integer part of the amount is as small as 29200 * possible without being below zero. This will result in the 29201 * largest units that can represent this measurement without 29202 * fractions. Measurements can only be scaled to other measurements 29203 * of the same type. 29204 * 29205 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29206 * or undefined if the system can be inferred from the current measure 29207 * @return {Measurement} a new instance that is scaled to the 29208 * right level 29209 */ 29210 DigitalStorageUnit.prototype.scale = function(measurementsystem) { 29211 var mSystem; 29212 if (this.unit in DigitalStorageUnit.bitSystem) { 29213 mSystem = DigitalStorageUnit.bitSystem; 29214 } else { 29215 mSystem = DigitalStorageUnit.byteSystem; 29216 } 29217 29218 var dStorage = this.amount; 29219 var munit = this.unit; 29220 var fromRow = DigitalStorageUnit.ratios[this.unit]; 29221 29222 dStorage = 18446744073709551999; 29223 for (var m in mSystem) { 29224 var tmp = this.amount * fromRow[mSystem[m]]; 29225 if (tmp >= 1 && tmp < dStorage) { 29226 dStorage = tmp; 29227 munit = m; 29228 } 29229 } 29230 29231 return new DigitalStorageUnit({ 29232 unit: munit, 29233 amount: dStorage 29234 }); 29235 }; 29236 29237 DigitalStorageUnit.aliases = { 29238 "bits": "bit", 29239 "bit": "bit", 29240 "Bits": "bit", 29241 "Bit": "bit", 29242 "byte": "byte", 29243 "bytes": "byte", 29244 "Byte": "byte", 29245 "Bytes": "byte", 29246 "kilobits": "kilobit", 29247 "Kilobits": "kilobit", 29248 "KiloBits": "kilobit", 29249 "kiloBits": "kilobit", 29250 "kilobit": "kilobit", 29251 "Kilobit": "kilobit", 29252 "kiloBit": "kilobit", 29253 "KiloBit": "kilobit", 29254 "kb": "kilobit", 29255 "Kb": "kilobit", 29256 "kilobyte": "kilobyte", 29257 "Kilobyte": "kilobyte", 29258 "kiloByte": "kilobyte", 29259 "KiloByte": "kilobyte", 29260 "kilobytes": "kilobyte", 29261 "Kilobytes": "kilobyte", 29262 "kiloBytes": "kilobyte", 29263 "KiloBytes": "kilobyte", 29264 "kB": "kilobyte", 29265 "KB": "kilobyte", 29266 "megabit": "megabit", 29267 "Megabit": "megabit", 29268 "megaBit": "megabit", 29269 "MegaBit": "megabit", 29270 "megabits": "megabit", 29271 "Megabits": "megabit", 29272 "megaBits": "megabit", 29273 "MegaBits": "megabit", 29274 "Mb": "megabit", 29275 "mb": "megabit", 29276 "megabyte": "megabyte", 29277 "Megabyte": "megabyte", 29278 "megaByte": "megabyte", 29279 "MegaByte": "megabyte", 29280 "megabytes": "megabyte", 29281 "Megabytes": "megabyte", 29282 "megaBytes": "megabyte", 29283 "MegaBytes": "megabyte", 29284 "MB": "megabyte", 29285 "mB": "megabyte", 29286 "gigabit": "gigabit", 29287 "Gigabit": "gigabit", 29288 "gigaBit": "gigabit", 29289 "GigaBit": "gigabit", 29290 "gigabits": "gigabit", 29291 "Gigabits": "gigabit", 29292 "gigaBits": "gigabyte", 29293 "GigaBits": "gigabit", 29294 "Gb": "gigabit", 29295 "gb": "gigabit", 29296 "gigabyte": "gigabyte", 29297 "Gigabyte": "gigabyte", 29298 "gigaByte": "gigabyte", 29299 "GigaByte": "gigabyte", 29300 "gigabytes": "gigabyte", 29301 "Gigabytes": "gigabyte", 29302 "gigaBytes": "gigabyte", 29303 "GigaBytes": "gigabyte", 29304 "GB": "gigabyte", 29305 "gB": "gigabyte", 29306 "terabit": "terabit", 29307 "Terabit": "terabit", 29308 "teraBit": "terabit", 29309 "TeraBit": "terabit", 29310 "terabits": "terabit", 29311 "Terabits": "terabit", 29312 "teraBits": "terabit", 29313 "TeraBits": "terabit", 29314 "tb": "terabit", 29315 "Tb": "terabit", 29316 "terabyte": "terabyte", 29317 "Terabyte": "terabyte", 29318 "teraByte": "terabyte", 29319 "TeraByte": "terabyte", 29320 "terabytes": "terabyte", 29321 "Terabytes": "terabyte", 29322 "teraBytes": "terabyte", 29323 "TeraBytes": "terabyte", 29324 "TB": "terabyte", 29325 "tB": "terabyte", 29326 "petabit": "petabit", 29327 "Petabit": "petabit", 29328 "petaBit": "petabit", 29329 "PetaBit": "petabit", 29330 "petabits": "petabit", 29331 "Petabits": "petabit", 29332 "petaBits": "petabit", 29333 "PetaBits": "petabit", 29334 "pb": "petabit", 29335 "Pb": "petabit", 29336 "petabyte": "petabyte", 29337 "Petabyte": "petabyte", 29338 "petaByte": "petabyte", 29339 "PetaByte": "petabyte", 29340 "petabytes": "petabyte", 29341 "Petabytes": "petabyte", 29342 "petaBytes": "petabyte", 29343 "PetaBytes": "petabyte", 29344 "PB": "petabyte", 29345 "pB": "petabyte" 29346 }; 29347 29348 /** 29349 * Convert a digitalStorage to another measure. 29350 * @static 29351 * @param to {string} unit to convert to 29352 * @param from {string} unit to convert from 29353 * @param digitalStorage {number} amount to be convert 29354 * @returns {number|undefined} the converted amount 29355 */ 29356 DigitalStorageUnit.convert = function(to, from, digitalStorage) { 29357 from = DigitalStorageUnit.aliases[from] || from; 29358 to = DigitalStorageUnit.aliases[to] || to; 29359 var fromRow = DigitalStorageUnit.ratios[from]; 29360 var toRow = DigitalStorageUnit.ratios[to]; 29361 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29362 return undefined; 29363 } 29364 var result = digitalStorage * fromRow[toRow[0]]; 29365 return result; 29366 }; 29367 29368 /** 29369 * @private 29370 * @static 29371 */ 29372 DigitalStorageUnit.getMeasures = function () { 29373 var ret = []; 29374 for (var m in DigitalStorageUnit.ratios) { 29375 ret.push(m); 29376 } 29377 return ret; 29378 }; 29379 29380 //register with the factory method 29381 Measurement._constructors["digitalStorage"] = DigitalStorageUnit; 29382 29383 29384 /*< EnergyUnit.js */ 29385 /* 29386 * Energy.js - Unit conversions for Energys/energys 29387 * 29388 * Copyright © 2014-2015, JEDLSoft 29389 * 29390 * Licensed under the Apache License, Version 2.0 (the "License"); 29391 * you may not use this file except in compliance with the License. 29392 * You may obtain a copy of the License at 29393 * 29394 * http://www.apache.org/licenses/LICENSE-2.0 29395 * 29396 * Unless required by applicable law or agreed to in writing, software 29397 * distributed under the License is distributed on an "AS IS" BASIS, 29398 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29399 * 29400 * See the License for the specific language governing permissions and 29401 * limitations under the License. 29402 */ 29403 29404 /* 29405 !depends 29406 Measurement.js 29407 */ 29408 29409 29410 /** 29411 * @class 29412 * Create a new energy measurement instance. 29413 * 29414 * @constructor 29415 * @extends Measurement 29416 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29417 * the construction of this instance 29418 */ 29419 var EnergyUnit = function (options) { 29420 this.unit = "joule"; 29421 this.amount = 0; 29422 this.aliases = EnergyUnit.aliases; // share this table in all instances 29423 29424 if (options) { 29425 if (typeof(options.unit) !== 'undefined') { 29426 this.originalUnit = options.unit; 29427 this.unit = this.aliases[options.unit] || options.unit; 29428 } 29429 29430 if (typeof(options.amount) === 'object') { 29431 if (options.amount.getMeasure() === "energy") { 29432 this.amount = EnergyUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29433 } else { 29434 throw "Cannot convert units " + options.amount.unit + " to a energy"; 29435 } 29436 } else if (typeof(options.amount) !== 'undefined') { 29437 this.amount = parseFloat(options.amount); 29438 } 29439 } 29440 29441 if (typeof(EnergyUnit.ratios[this.unit]) === 'undefined') { 29442 throw "Unknown unit: " + options.unit; 29443 } 29444 }; 29445 29446 EnergyUnit.prototype = new Measurement(); 29447 EnergyUnit.prototype.parent = Measurement; 29448 EnergyUnit.prototype.constructor = EnergyUnit; 29449 29450 EnergyUnit.ratios = { 29451 /* index mJ J BTU kJ Wh Cal MJ kWh gJ MWh GWh */ 29452 "millijoule": [ 1, 1, 0.001, 9.4781707775e-7, 1e-6, 2.7777777778e-7, 2.3884589663e-7, 1.0e-9, 2.7777777778e-10, 1.0e-12, 2.7777777778e-13, 2.7777777778e-16 ], 29453 "joule": [ 2, 1000, 1, 9.4781707775e-4, 0.001, 2.7777777778e-4, 2.3884589663e-4, 1.0e-6, 2.7777777778e-7, 1.0e-9, 2.7777777778e-10, 2.7777777778e-13 ], 29454 "BTU": [ 3, 1055055.9, 1055.0559, 1, 1.0550559, 0.29307108333, 0.25199577243, 1.0550559e-3, 2.9307108333e-4, 1.0550559e-6, 2.9307108333e-7, 2.9307108333e-10 ], 29455 "kilojoule": [ 4, 1000000, 1000, 0.94781707775, 1, 0.27777777778, 0.23884589663, 0.001, 2.7777777778e-4, 1.0e-6, 2.7777777778e-7, 2.7777777778e-10 ], 29456 "watt hour": [ 5, 3.6e+6, 3600, 3.4121414799, 3.6, 1, 0.85984522786, 0.0036, 0.001, 3.6e-6, 1.0e-6, 1.0e-9 ], 29457 "calorie": [ 6, 4.868e+5, 4186.8, 3.9683205411, 4.1868, 1.163, 1, 4.1868e-3, 1.163e-3, 4.1868e-6, 1.163e-6, 1.163e-9 ], 29458 "megajoule": [ 7, 1e+9, 1e+6, 947.81707775, 1000, 277.77777778, 238.84589663, 1, 0.27777777778, 0.001, 2.7777777778e-4, 2.7777777778e-7 ], 29459 "kilowatt hour":[ 8, 3.6e+9, 3.6e+6, 3412.1414799, 3600, 1000, 859.84522786, 3.6, 1, 3.6e-3, 0.001, 1e-6 ], 29460 "gigajoule": [ 9, 1e+12, 1e+9, 947817.07775, 1e+6, 277777.77778, 238845.89663, 1000, 277.77777778, 1, 0.27777777778, 2.7777777778e-4 ], 29461 "megawatt hour":[ 10, 3.6e+12, 3.6e+9, 3412141.4799, 3.6e+6, 1e+6, 859845.22786, 3600, 1000, 3.6, 1, 0.001 ], 29462 "gigawatt hour":[ 11, 3.6e+15, 3.6e+12, 3412141479.9, 3.6e+9, 1e+9, 859845227.86, 3.6e+6, 1e+6, 3600, 1000, 1 ] 29463 }; 29464 29465 /** 29466 * Return the type of this measurement. Examples are "mass", 29467 * "length", "speed", etc. Measurements can only be converted 29468 * to measurements of the same type.<p> 29469 * 29470 * The type of the units is determined automatically from the 29471 * units. For example, the unit "grams" is type "mass". Use the 29472 * static call {@link Measurement.getAvailableUnits} 29473 * to find out what units this version of ilib supports. 29474 * 29475 * @return {string} the name of the type of this measurement 29476 */ 29477 EnergyUnit.prototype.getMeasure = function() { 29478 return "energy"; 29479 }; 29480 29481 /** 29482 * Return a new measurement instance that is converted to a new 29483 * measurement unit. Measurements can only be converted 29484 * to measurements of the same type.<p> 29485 * 29486 * @param {string} to The name of the units to convert to 29487 * @return {Measurement|undefined} the converted measurement 29488 * or undefined if the requested units are for a different 29489 * measurement type 29490 */ 29491 EnergyUnit.prototype.convert = function(to) { 29492 if (!to || typeof(EnergyUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29493 return undefined; 29494 } 29495 return new EnergyUnit({ 29496 unit: to, 29497 amount: this 29498 }); 29499 }; 29500 29501 EnergyUnit.aliases = { 29502 "milli joule": "millijoule", 29503 "millijoule": "millijoule", 29504 "MilliJoule": "millijoule", 29505 "milliJ": "millijoule", 29506 "joule": "joule", 29507 "J": "joule", 29508 "j": "joule", 29509 "Joule": "joule", 29510 "Joules": "joule", 29511 "joules": "joule", 29512 "BTU": "BTU", 29513 "btu": "BTU", 29514 "British thermal unit": "BTU", 29515 "british thermal unit": "BTU", 29516 "kilo joule": "kilojoule", 29517 "kJ": "kilojoule", 29518 "kj": "kilojoule", 29519 "Kj": "kilojoule", 29520 "kiloJoule": "kilojoule", 29521 "kilojoule": "kilojoule", 29522 "kjoule": "kilojoule", 29523 "watt hour": "watt hour", 29524 "Wh": "watt hour", 29525 "wh": "watt hour", 29526 "watt-hour": "watt hour", 29527 "calorie": "calorie", 29528 "Cal": "calorie", 29529 "cal": "calorie", 29530 "Calorie": "calorie", 29531 "calories": "calorie", 29532 "mega joule": "megajoule", 29533 "MJ": "megajoule", 29534 "megajoule": "megajoule", 29535 "megajoules": "megajoule", 29536 "Megajoules": "megajoule", 29537 "megaJoules": "megajoule", 29538 "MegaJoules": "megajoule", 29539 "megaJoule": "megajoule", 29540 "MegaJoule": "megajoule", 29541 "kilo Watt hour": "kilowatt hour", 29542 "kWh": "kilowatt hour", 29543 "kiloWh": "kilowatt hour", 29544 "KiloWh": "kilowatt hour", 29545 "KiloWatt-hour": "kilowatt hour", 29546 "kilowatt hour": "kilowatt hour", 29547 "kilowatt-hour": "kilowatt hour", 29548 "KiloWatt-hours": "kilowatt hour", 29549 "kilowatt-hours": "kilowatt hour", 29550 "Kilo Watt-hour": "kilowatt hour", 29551 "Kilo Watt-hours": "kilowatt hour", 29552 "giga joule": "gigajoule", 29553 "gJ": "gigajoule", 29554 "GJ": "gigajoule", 29555 "GigaJoule": "gigajoule", 29556 "gigaJoule": "gigajoule", 29557 "gigajoule": "gigajoule", 29558 "GigaJoules": "gigajoule", 29559 "gigaJoules": "gigajoule", 29560 "Gigajoules": "gigajoule", 29561 "gigajoules": "gigajoule", 29562 "mega watt hour": "megawatt hour", 29563 "MWh": "megawatt hour", 29564 "MegaWh": "megawatt hour", 29565 "megaWh": "megawatt hour", 29566 "megaWatthour": "megawatt hour", 29567 "megaWatt-hour": "megawatt hour", 29568 "mega Watt-hour": "megawatt hour", 29569 "megaWatt hour": "megawatt hour", 29570 "megawatt hour": "megawatt hour", 29571 "mega Watt hour": "megawatt hour", 29572 "giga watt hour": "gigawatt hour", 29573 "gWh": "gigawatt hour", 29574 "GWh": "gigawatt hour", 29575 "gigaWh": "gigawatt hour", 29576 "gigaWatt-hour": "gigawatt hour", 29577 "gigawatt-hour": "gigawatt hour", 29578 "gigaWatt hour": "gigawatt hour", 29579 "gigawatt hour": "gigawatt hour", 29580 "gigawatthour": "gigawatt hour" 29581 }; 29582 29583 /** 29584 * Convert a energy to another measure. 29585 * @static 29586 * @param to {string} unit to convert to 29587 * @param from {string} unit to convert from 29588 * @param energy {number} amount to be convert 29589 * @returns {number|undefined} the converted amount 29590 */ 29591 EnergyUnit.convert = function(to, from, energy) { 29592 from = EnergyUnit.aliases[from] || from; 29593 to = EnergyUnit.aliases[to] || to; 29594 var fromRow = EnergyUnit.ratios[from]; 29595 var toRow = EnergyUnit.ratios[to]; 29596 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29597 return undefined; 29598 } 29599 return energy * fromRow[toRow[0]]; 29600 }; 29601 29602 /** 29603 * @private 29604 * @static 29605 */ 29606 EnergyUnit.getMeasures = function () { 29607 var ret = []; 29608 for (var m in EnergyUnit.ratios) { 29609 ret.push(m); 29610 } 29611 return ret; 29612 }; 29613 29614 EnergyUnit.metricJouleSystem = { 29615 "millijoule": 1, 29616 "joule": 2, 29617 "kilojoule": 4, 29618 "megajoule": 7, 29619 "gigajoule": 9 29620 }; 29621 EnergyUnit.metricWattHourSystem = { 29622 "watt hour": 5, 29623 "kilowatt hour": 8, 29624 "megawatt hour": 10, 29625 "gigawatt hour": 11 29626 }; 29627 29628 EnergyUnit.imperialSystem = { 29629 "BTU": 3 29630 }; 29631 EnergyUnit.uscustomarySystem = { 29632 "calorie": 6 29633 }; 29634 29635 EnergyUnit.metricToImperial = { 29636 "millijoule": "BTU", 29637 "joule": "BTU", 29638 "kilojoule": "BTU", 29639 "megajoule": "BTU", 29640 "gigajoule": "BTU" 29641 }; 29642 EnergyUnit.imperialToMetric = { 29643 "BTU": "joule" 29644 }; 29645 29646 /** 29647 * Localize the measurement to the commonly used measurement in that locale. For example 29648 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29649 * the formatted number should be automatically converted to the most appropriate 29650 * measure in the other system, in this case, mph. The formatted result should 29651 * appear as "37.3 mph". 29652 * 29653 * @param {string} locale current locale string 29654 * @returns {Measurement} a new instance that is converted to locale 29655 */ 29656 EnergyUnit.prototype.localize = function(locale) { 29657 var to; 29658 if (locale === "en-GB") { 29659 to = EnergyUnit.metricToImperial[this.unit] || this.unit; 29660 } else { 29661 to = EnergyUnit.imperialToMetric[this.unit] || this.unit; 29662 } 29663 29664 return new EnergyUnit({ 29665 unit: to, 29666 amount: this 29667 }); 29668 }; 29669 29670 /** 29671 * Scale the measurement unit to an acceptable level. The scaling 29672 * happens so that the integer part of the amount is as small as 29673 * possible without being below zero. This will result in the 29674 * largest units that can represent this measurement without 29675 * fractions. Measurements can only be scaled to other measurements 29676 * of the same type. 29677 * 29678 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29679 * or undefined if the system can be inferred from the current measure 29680 * @return {Measurement} a new instance that is scaled to the 29681 * right level 29682 */ 29683 EnergyUnit.prototype.scale = function(measurementsystem) { 29684 var fromRow = EnergyUnit.ratios[this.unit]; 29685 var mSystem; 29686 29687 if ((measurementsystem === "metric" && typeof(EnergyUnit.metricJouleSystem[this.unit]) !== 'undefined')|| (typeof(measurementsystem) === 'undefined' 29688 && typeof(EnergyUnit.metricJouleSystem[this.unit]) !== 'undefined')) { 29689 mSystem = EnergyUnit.metricJouleSystem; 29690 } 29691 else if ((measurementsystem === "metric" && typeof(EnergyUnit.metricWattHourSystem[this.unit]) !== 'undefined')|| (typeof(measurementsystem) === 'undefined' 29692 && typeof(EnergyUnit.metricWattHourSystem[this.unit]) !== 'undefined')) { 29693 mSystem = EnergyUnit.metricWattHourSystem; 29694 } 29695 29696 else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 29697 && typeof(EnergyUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 29698 mSystem = EnergyUnit.uscustomarySystem; 29699 } 29700 else if (measurementsystem === "imperial"|| (typeof(measurementsystem) === 'undefined' 29701 && typeof(EnergyUnit.imperialSystem[this.unit]) !== 'undefined')) { 29702 mSystem = EnergyUnit.imperialSystem; 29703 } 29704 29705 var energy = this.amount; 29706 var munit = this.unit; 29707 29708 energy = 18446744073709551999; 29709 29710 for (var m in mSystem) { 29711 var tmp = this.amount * fromRow[mSystem[m]]; 29712 if (tmp >= 1 && tmp < energy) { 29713 energy = tmp; 29714 munit = m; 29715 } 29716 } 29717 29718 return new EnergyUnit({ 29719 unit: munit, 29720 amount: energy 29721 }); 29722 }; 29723 //register with the factory method 29724 Measurement._constructors["energy"] = EnergyUnit; 29725 29726 29727 /*< FuelConsumptionUnit.js */ 29728 /* 29729 * fuelconsumption.js - Unit conversions for FuelConsumption 29730 * 29731 * Copyright © 2014-2015, JEDLSoft 29732 * 29733 * Licensed under the Apache License, Version 2.0 (the "License"); 29734 * you may not use this file except in compliance with the License. 29735 * You may obtain a copy of the License at 29736 * 29737 * http://www.apache.org/licenses/LICENSE-2.0 29738 * 29739 * Unless required by applicable law or agreed to in writing, software 29740 * distributed under the License is distributed on an "AS IS" BASIS, 29741 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29742 * 29743 * See the License for the specific language governing permissions and 29744 * limitations under the License. 29745 */ 29746 29747 /* 29748 !depends 29749 Measurement.js 29750 */ 29751 29752 29753 /** 29754 * @class 29755 * Create a new fuelconsumption measurement instance. 29756 * 29757 * @constructor 29758 * @extends Measurement 29759 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29760 * the construction of this instance 29761 */ 29762 var FuelConsumptionUnit = function(options) { 29763 this.unit = "km/liter"; 29764 this.amount = 0; 29765 this.aliases = FuelConsumptionUnit.aliases; // share this table in all instances 29766 29767 if (options) { 29768 if (typeof(options.unit) !== 'undefined') { 29769 this.originalUnit = options.unit; 29770 this.unit = this.aliases[options.unit] || options.unit; 29771 } 29772 29773 if (typeof(options.amount) === 'object') { 29774 if (options.amount.getMeasure() === "fuelconsumption") { 29775 this.amount = FuelConsumptionUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29776 } else { 29777 throw "Cannot convert unit " + options.amount.unit + " to fuelconsumption"; 29778 } 29779 } else if (typeof(options.amount) !== 'undefined') { 29780 this.amount = parseFloat(options.amount); 29781 } 29782 } 29783 }; 29784 29785 FuelConsumptionUnit.prototype = new Measurement(); 29786 FuelConsumptionUnit.prototype.parent = Measurement; 29787 FuelConsumptionUnit.prototype.constructor = FuelConsumptionUnit; 29788 29789 FuelConsumptionUnit.ratios = [ 29790 "km/liter", 29791 "liter/100km", 29792 "mpg", 29793 "mpg(imp)" 29794 ]; 29795 29796 /** 29797 * Return the type of this measurement. Examples are "mass", 29798 * "length", "speed", etc. Measurements can only be converted 29799 * to measurements of the same type.<p> 29800 * 29801 * The type of the units is determined automatically from the 29802 * units. For example, the unit "grams" is type "mass". Use the 29803 * static call {@link Measurement.getAvailableUnits} 29804 * to find out what units this version of ilib supports. 29805 * 29806 * @return {string} the name of the type of this measurement 29807 */ 29808 FuelConsumptionUnit.prototype.getMeasure = function() { 29809 return "fuelconsumption"; 29810 }; 29811 29812 /** 29813 * Return a new measurement instance that is converted to a new 29814 * measurement unit. Measurements can only be converted 29815 * to measurements of the same type.<p> 29816 * 29817 * @param {string} to The name of the units to convert to 29818 * @return {Measurement|undefined} the converted measurement 29819 * or undefined if the requested units are for a different 29820 * measurement type 29821 */ 29822 FuelConsumptionUnit.prototype.convert = function(to) { 29823 if (!to || typeof(FuelConsumptionUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29824 return undefined; 29825 } 29826 return new FuelConsumptionUnit({ 29827 unit: to, 29828 amount: this 29829 }); 29830 }; 29831 /*["km/liter", "liter/100km", "mpg", "mpg(imp)"*/ 29832 FuelConsumptionUnit.aliases = { 29833 "Km/liter": "km/liter", 29834 "KM/Liter": "km/liter", 29835 "KM/L": "km/liter", 29836 "Kilometers Per Liter": "km/liter", 29837 "kilometers per liter": "km/liter", 29838 "km/l": "km/liter", 29839 "Kilometers/Liter": "km/liter", 29840 "Kilometer/Liter": "km/liter", 29841 "kilometers/liter": "km/liter", 29842 "kilometer/liter": "km/liter", 29843 "km/liter": "km/liter", 29844 "Liter/100km": "liter/100km", 29845 "Liters/100km": "liter/100km", 29846 "Liter/100kms": "liter/100km", 29847 "Liters/100kms": "liter/100km", 29848 "liter/100km": "liter/100km", 29849 "liters/100kms": "liter/100km", 29850 "liters/100km": "liter/100km", 29851 "liter/100kms": "liter/100km", 29852 "Liter/100KM": "liter/100km", 29853 "Liters/100KM": "liter/100km", 29854 "L/100km": "liter/100km", 29855 "L/100KM": "liter/100km", 29856 "l/100KM": "liter/100km", 29857 "l/100km": "liter/100km", 29858 "l/100kms": "liter/100km", 29859 "MPG(US)": "mpg", 29860 "USMPG ": "mpg", 29861 "mpg": "mpg", 29862 "mpgUS": "mpg", 29863 "mpg(US)": "mpg", 29864 "mpg(us)": "mpg", 29865 "mpg-us": "mpg", 29866 "mpg Imp": "mpg(imp)", 29867 "MPG(imp)": "mpg(imp)", 29868 "mpg(imp)": "mpg(imp)", 29869 "mpg-imp": "mpg(imp)" 29870 }; 29871 29872 FuelConsumptionUnit.metricToUScustomary = { 29873 "km/liter": "mpg", 29874 "liter/100km": "mpg" 29875 }; 29876 FuelConsumptionUnit.metricToImperial = { 29877 "km/liter": "mpg(imp)", 29878 "liter/100km": "mpg(imp)" 29879 }; 29880 29881 FuelConsumptionUnit.imperialToMetric = { 29882 "mpg(imp)": "km/liter" 29883 }; 29884 FuelConsumptionUnit.imperialToUScustomary = { 29885 "mpg(imp)": "mpg" 29886 }; 29887 29888 FuelConsumptionUnit.uScustomaryToImperial = { 29889 "mpg": "mpg(imp)" 29890 }; 29891 FuelConsumptionUnit.uScustomarylToMetric = { 29892 "mpg": "km/liter" 29893 }; 29894 29895 /** 29896 * Localize the measurement to the commonly used measurement in that locale. For example 29897 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29898 * the formatted number should be automatically converted to the most appropriate 29899 * measure in the other system, in this case, mph. The formatted result should 29900 * appear as "37.3 mph". 29901 * 29902 * @param {string} locale current locale string 29903 * @returns {Measurement} a new instance that is converted to locale 29904 */ 29905 FuelConsumptionUnit.prototype.localize = function(locale) { 29906 var to; 29907 if (locale === "en-US") { 29908 to = FuelConsumptionUnit.metricToUScustomary[this.unit] || 29909 FuelConsumptionUnit.imperialToUScustomary[this.unit] || 29910 this.unit; 29911 } else if (locale === "en-GB") { 29912 to = FuelConsumptionUnit.metricToImperial[this.unit] || 29913 FuelConsumptionUnit.uScustomaryToImperial[this.unit] || 29914 this.unit; 29915 } else { 29916 to = FuelConsumptionUnit.uScustomarylToMetric[this.unit] || 29917 FuelConsumptionUnit.imperialToUScustomary[this.unit] || 29918 this.unit; 29919 } 29920 return new FuelConsumptionUnit({ 29921 unit: to, 29922 amount: this 29923 }); 29924 }; 29925 29926 /** 29927 * Convert a FuelConsumption to another measure. 29928 * 29929 * @static 29930 * @param to {string} unit to convert to 29931 * @param from {string} unit to convert from 29932 * @param fuelConsumption {number} amount to be convert 29933 * @returns {number|undefined} the converted amount 29934 */ 29935 FuelConsumptionUnit.convert = function(to, from, fuelConsumption) { 29936 from = FuelConsumptionUnit.aliases[from] || from; 29937 to = FuelConsumptionUnit.aliases[to] || to; 29938 var returnValue = 0; 29939 29940 switch (from) { 29941 case "km/liter": 29942 switch (to) { 29943 case "km/liter": 29944 returnValue = fuelConsumption * 1; 29945 break; 29946 case "liter/100km": 29947 returnValue = 100 / fuelConsumption; 29948 break; 29949 case "mpg": 29950 returnValue = fuelConsumption * 2.35215; 29951 break; 29952 case "mpg(imp)": 29953 returnValue = fuelConsumption * 2.82481; 29954 break; 29955 } 29956 break; 29957 case "liter/100km": 29958 switch (to) { 29959 case "km/liter": 29960 returnValue = 100 / fuelConsumption; 29961 break; 29962 case "liter/100km": 29963 returnValue = fuelConsumption * 1; 29964 break; 29965 case "mpg": 29966 returnValue = 235.215 / fuelConsumption; 29967 break; 29968 case "mpg(imp)": 29969 returnValue = 282.481 / fuelConsumption; 29970 break; 29971 } 29972 break; 29973 case "mpg": 29974 switch (to) { 29975 case "km/liter": 29976 returnValue = fuelConsumption * 0.425144; 29977 break; 29978 case "liter/100km": 29979 returnValue = 235.215 / fuelConsumption; 29980 break; 29981 case "mpg": 29982 returnValue = 1 * fuelConsumption; 29983 break; 29984 case "mpg(imp)": 29985 returnValue = 1.20095 * fuelConsumption; 29986 break; 29987 } 29988 break; 29989 case "mpg(imp)": 29990 switch (to) { 29991 case "km/liter": 29992 returnValue = fuelConsumption * 0.354006; 29993 break; 29994 case "liter/100km": 29995 returnValue = 282.481 / fuelConsumption; 29996 break; 29997 case "mpg": 29998 returnValue = 0.832674 * fuelConsumption; 29999 break; 30000 case "mpg(imp)": 30001 returnValue = 1 * fuelConsumption; 30002 break; 30003 } 30004 break; 30005 } 30006 return returnValue; 30007 }; 30008 30009 /** 30010 * Scale the measurement unit to an acceptable level. The scaling 30011 * happens so that the integer part of the amount is as small as 30012 * possible without being below zero. This will result in the 30013 * largest units that can represent this measurement without 30014 * fractions. Measurements can only be scaled to other measurements 30015 * of the same type. 30016 * 30017 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30018 * or undefined if the system can be inferred from the current measure 30019 * @return {Measurement} a new instance that is scaled to the 30020 * right level 30021 */ 30022 FuelConsumptionUnit.prototype.scale = function(measurementsystem) { 30023 return new FuelConsumptionUnit({ 30024 unit: this.unit, 30025 amount: this.amount 30026 }); 30027 }; 30028 30029 /** 30030 * @private 30031 * @static 30032 */ 30033 FuelConsumptionUnit.getMeasures = function() { 30034 var ret = []; 30035 ret.push("km/liter"); 30036 ret.push("liter/100km"); 30037 ret.push("mpg"); 30038 ret.push("mpg(imp)"); 30039 30040 return ret; 30041 }; 30042 30043 //register with the factory method 30044 Measurement._constructors["fuelconsumption"] = FuelConsumptionUnit; 30045 30046 30047 /*< LengthUnit.js */ 30048 /* 30049 * LengthUnit.js - Unit conversions for Lengths/lengths 30050 * 30051 * Copyright © 2014-2015, JEDLSoft 30052 * 30053 * Licensed under the Apache License, Version 2.0 (the "License"); 30054 * you may not use this file except in compliance with the License. 30055 * You may obtain a copy of the License at 30056 * 30057 * http://www.apache.org/licenses/LICENSE-2.0 30058 * 30059 * Unless required by applicable law or agreed to in writing, software 30060 * distributed under the License is distributed on an "AS IS" BASIS, 30061 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30062 * 30063 * See the License for the specific language governing permissions and 30064 * limitations under the License. 30065 */ 30066 30067 /* 30068 !depends 30069 Measurement.js 30070 */ 30071 30072 30073 /** 30074 * @class 30075 * Create a new length measurement instance. 30076 * 30077 * @constructor 30078 * @extends Measurement 30079 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30080 * the construction of this instance 30081 */ 30082 var LengthUnit = function (options) { 30083 this.unit = "meter"; 30084 this.amount = 0; 30085 this.aliases = LengthUnit.aliases; // share this table in all instances 30086 30087 if (options) { 30088 if (typeof(options.unit) !== 'undefined') { 30089 this.originalUnit = options.unit; 30090 this.unit = this.aliases[options.unit] || options.unit; 30091 } 30092 30093 if (typeof(options.amount) === 'object') { 30094 if (options.amount.getMeasure() === "length") { 30095 this.amount = LengthUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30096 } else { 30097 throw "Cannot convert unit " + options.amount.unit + " to a length"; 30098 } 30099 } else if (typeof(options.amount) !== 'undefined') { 30100 this.amount = parseFloat(options.amount); 30101 } 30102 } 30103 30104 if (typeof(LengthUnit.ratios[this.unit]) === 'undefined') { 30105 throw "Unknown unit: " + options.unit; 30106 } 30107 }; 30108 30109 LengthUnit.prototype = new Measurement(); 30110 LengthUnit.prototype.parent = Measurement; 30111 LengthUnit.prototype.constructor = LengthUnit; 30112 30113 LengthUnit.ratios = { 30114 /* index, µm mm cm inch dm foot yard m dam hm km mile nm Mm Gm */ 30115 "micrometer": [ 1, 1, 1e-3, 1e-4, 3.93701e-5, 1e-5, 3.28084e-6, 1.09361e-6, 1e-6, 1e-7, 1e-8, 1e-9, 6.21373e-10, 5.39957e-10, 1e-12, 1e-15 ], 30116 "millimeter": [ 2, 1000, 1, 0.1, 0.0393701, 0.01, 0.00328084, 1.09361e-3, 0.001, 1e-4, 1e-5, 1e-6, 6.21373e-7, 5.39957e-7, 1e-9, 1e-12 ], 30117 "centimeter": [ 3, 1e4, 10, 1, 0.393701, 0.1, 0.0328084, 0.0109361, 0.01, 0.001, 1e-4, 1e-5, 6.21373e-6, 5.39957e-6, 1e-8, 1e-9 ], 30118 "inch": [ 4, 25399.986, 25.399986, 2.5399986, 1, 0.25399986, 0.083333333, 0.027777778, 0.025399986, 2.5399986e-3, 2.5399986e-4, 2.5399986e-5, 1.5783e-5, 1.3715e-5, 2.5399986e-8, 2.5399986e-11 ], 30119 "decimeter": [ 5, 1e5, 100, 10, 3.93701, 1, 0.328084, 0.109361, 0.1, 0.01, 0.001, 1e-4, 6.21373e-5, 5.39957e-5, 1e-7, 1e-8 ], 30120 "foot": [ 6, 304799.99, 304.79999, 30.479999, 12, 3.0479999, 1, 0.33333333, 0.30479999, 0.030479999, 3.0479999e-3, 3.0479999e-4, 1.89394e-4, 1.64579e-4, 3.0479999e-7, 3.0479999e-10 ], 30121 "yard": [ 7, 914402.758, 914.402758, 91.4402758, 36, 9.14402758, 3, 1, 0.914402758, 0.0914402758, 9.14402758e-3, 9.14402758e-4, 5.68182e-4, 4.93737e-4, 9.14402758e-7, 9.14402758e-10 ], 30122 "meter": [ 8, 1e6, 1000, 100, 39.3701, 10, 3.28084, 1.09361, 1, 0.1, 0.01, 0.001, 6.213712e-4, 5.39957e-4, 1e-6, 1e-7 ], 30123 "decameter": [ 9, 1e7, 1e4, 1000, 393.701, 100, 32.8084, 10.9361, 10, 1, 0.1, 0.01, 6.21373e-3, 5.39957e-3, 1e-5, 1e-6 ], 30124 "hectometer": [ 10, 1e8, 1e5, 1e4, 3937.01, 1000, 328.084, 109.361, 100, 10, 1, 0.1, 0.0621373, 0.0539957, 1e-4, 1e-5 ], 30125 "kilometer": [ 11, 1e9, 1e6, 1e5, 39370.1, 1e4, 3280.84, 1093.61, 1000, 100, 10, 1, 0.621373, 0.539957, 0.001, 1e-4 ], 30126 "mile": [ 12, 1.60934e9, 1.60934e6, 1.60934e5, 63360, 1.60934e4, 5280, 1760, 1609.34, 160.934, 16.0934, 1.60934, 1, 0.868976, 1.60934e-3, 1.60934e-6 ], 30127 "nauticalmile": [ 13, 1.852e9, 1.852e6, 1.852e5, 72913.4, 1.852e4, 6076.12, 2025.37, 1852, 185.2, 18.52, 1.852, 1.15078, 1, 1.852e-3, 1.852e-6 ], 30128 "megameter": [ 14, 1e12, 1e9, 1e6, 3.93701e7, 1e5, 3.28084e6, 1.09361e6, 1e4, 1000, 100, 10, 621.373, 539.957, 1, 0.001 ], 30129 "gigameter": [ 15, 1e15, 1e12, 1e9, 3.93701e10, 1e8, 3.28084e9, 1.09361e9, 1e7, 1e6, 1e5, 1e4, 621373.0, 539957.0, 1000, 1 ] 30130 }; 30131 30132 LengthUnit.metricSystem = { 30133 "micrometer": 1, 30134 "millimeter": 2, 30135 "centimeter": 3, 30136 "decimeter": 5, 30137 "meter": 8, 30138 "decameter": 9, 30139 "hectometer": 10, 30140 "kilometer": 11, 30141 "megameter": 14, 30142 "gigameter": 15 30143 }; 30144 LengthUnit.imperialSystem = { 30145 "inch": 4, 30146 "foot": 6, 30147 "yard": 7, 30148 "mile": 12, 30149 "nauticalmile": 13 30150 }; 30151 LengthUnit.uscustomarySystem = { 30152 "inch": 4, 30153 "foot": 6, 30154 "yard": 7, 30155 "mile": 12, 30156 "nauticalmile": 13 30157 }; 30158 30159 LengthUnit.metricToUScustomary = { 30160 "micrometer": "inch", 30161 "millimeter": "inch", 30162 "centimeter": "inch", 30163 "decimeter": "inch", 30164 "meter": "yard", 30165 "decameter": "yard", 30166 "hectometer": "mile", 30167 "kilometer": "mile", 30168 "megameter": "nauticalmile", 30169 "gigameter": "nauticalmile" 30170 }; 30171 LengthUnit.usCustomaryToMetric = { 30172 "inch": "centimeter", 30173 "foot": "centimeter", 30174 "yard": "meter", 30175 "mile": "kilometer", 30176 "nauticalmile": "kilometer" 30177 }; 30178 30179 /** 30180 * Return the type of this measurement. Examples are "mass", 30181 * "length", "speed", etc. Measurements can only be converted 30182 * to measurements of the same type.<p> 30183 * 30184 * The type of the units is determined automatically from the 30185 * units. For example, the unit "grams" is type "mass". Use the 30186 * static call {@link Measurement.getAvailableUnits} 30187 * to find out what units this version of ilib supports. 30188 * 30189 * @return {string} the name of the type of this measurement 30190 */ 30191 LengthUnit.prototype.getMeasure = function() { 30192 return "length"; 30193 }; 30194 30195 /** 30196 * Localize the measurement to the commonly used measurement in that locale. For example 30197 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30198 * the formatted number should be automatically converted to the most appropriate 30199 * measure in the other system, in this case, mph. The formatted result should 30200 * appear as "37.3 mph". 30201 * 30202 * @param {string} locale current locale string 30203 * @returns {Measurement} a new instance that is converted to locale 30204 */ 30205 LengthUnit.prototype.localize = function(locale) { 30206 var to; 30207 if (locale === "en-US" || locale === "en-GB") { 30208 to = LengthUnit.metricToUScustomary[this.unit] || this.unit; 30209 } else { 30210 to = LengthUnit.usCustomaryToMetric[this.unit] || this.unit; 30211 } 30212 return new LengthUnit({ 30213 unit: to, 30214 amount: this 30215 }); 30216 }; 30217 30218 /** 30219 * Return a new measurement instance that is converted to a new 30220 * measurement unit. Measurements can only be converted 30221 * to measurements of the same type.<p> 30222 * 30223 * @param {string} to The name of the units to convert to 30224 * @return {Measurement|undefined} the converted measurement 30225 * or undefined if the requested units are for a different 30226 * measurement type 30227 */ 30228 LengthUnit.prototype.convert = function(to) { 30229 if (!to || typeof(LengthUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30230 return undefined; 30231 } 30232 return new LengthUnit({ 30233 unit: to, 30234 amount: this 30235 }); 30236 }; 30237 30238 /** 30239 * Scale the measurement unit to an acceptable level. The scaling 30240 * happens so that the integer part of the amount is as small as 30241 * possible without being below zero. This will result in the 30242 * largest units that can represent this measurement without 30243 * fractions. Measurements can only be scaled to other measurements 30244 * of the same type. 30245 * 30246 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30247 * or undefined if the system can be inferred from the current measure 30248 * @return {Measurement} a new instance that is scaled to the 30249 * right level 30250 */ 30251 LengthUnit.prototype.scale = function(measurementsystem) { 30252 var mSystem; 30253 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 30254 && typeof(LengthUnit.metricSystem[this.unit]) !== 'undefined')) { 30255 mSystem = LengthUnit.metricSystem; 30256 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 30257 && typeof(LengthUnit.imperialSystem[this.unit]) !== 'undefined')) { 30258 mSystem = LengthUnit.imperialSystem; 30259 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 30260 && typeof(LengthUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 30261 mSystem = LengthUnit.uscustomarySystem; 30262 } else { 30263 return new LengthUnit({ 30264 unit: this.unit, 30265 amount: this.amount 30266 }); 30267 } 30268 30269 var length = this.amount; 30270 var munit = this.unit; 30271 var fromRow = LengthUnit.ratios[this.unit]; 30272 30273 length = 18446744073709551999; 30274 for (var m in mSystem) { 30275 var tmp = this.amount * fromRow[mSystem[m]]; 30276 if (tmp >= 1 && tmp < length) { 30277 length = tmp; 30278 munit = m; 30279 } 30280 } 30281 30282 return new LengthUnit({ 30283 unit: munit, 30284 amount: length 30285 }); 30286 }; 30287 30288 LengthUnit.aliases = { 30289 "miles": "mile", 30290 "mile":"mile", 30291 "nauticalmiles": "nauticalmile", 30292 "nautical mile": "nauticalmile", 30293 "nautical miles": "nauticalmile", 30294 "nauticalmile":"nauticalmile", 30295 "yards": "yard", 30296 "yard": "yard", 30297 "feet": "foot", 30298 "foot": "foot", 30299 "inches": "inch", 30300 "inch": "inch", 30301 "meters": "meter", 30302 "metre": "meter", 30303 "metres": "meter", 30304 "m": "meter", 30305 "meter": "meter", 30306 "micrometers": "micrometer", 30307 "micrometres": "micrometer", 30308 "micrometre": "micrometer", 30309 "µm": "micrometer", 30310 "micrometer": "micrometer", 30311 "millimeters": "millimeter", 30312 "millimetres": "millimeter", 30313 "millimetre": "millimeter", 30314 "mm": "millimeter", 30315 "millimeter": "millimeter", 30316 "centimeters": "centimeter", 30317 "centimetres": "centimeter", 30318 "centimetre": "centimeter", 30319 "cm": "centimeter", 30320 "centimeter": "centimeter", 30321 "decimeters": "decimeter", 30322 "decimetres": "decimeter", 30323 "decimetre": "decimeter", 30324 "dm": "decimeter", 30325 "decimeter": "decimeter", 30326 "decameters": "decameter", 30327 "decametres": "decameter", 30328 "decametre": "decameter", 30329 "dam": "decameter", 30330 "decameter": "decameter", 30331 "hectometers": "hectometer", 30332 "hectometres": "hectometer", 30333 "hectometre": "hectometer", 30334 "hm": "hectometer", 30335 "hectometer": "hectometer", 30336 "kilometers": "kilometer", 30337 "kilometres": "kilometer", 30338 "kilometre": "kilometer", 30339 "km": "kilometer", 30340 "kilometer": "kilometer", 30341 "megameters": "megameter", 30342 "megametres": "megameter", 30343 "megametre": "megameter", 30344 "Mm": "megameter", 30345 "megameter": "megameter", 30346 "gigameters": "gigameter", 30347 "gigametres": "gigameter", 30348 "gigametre": "gigameter", 30349 "Gm": "gigameter", 30350 "gigameter": "gigameter" 30351 }; 30352 30353 /** 30354 * Convert a length to another measure. 30355 * @static 30356 * @param to {string} unit to convert to 30357 * @param from {string} unit to convert from 30358 * @param length {number} amount to be convert 30359 * @returns {number|undefined} the converted amount 30360 */ 30361 LengthUnit.convert = function(to, from, length) { 30362 from = LengthUnit.aliases[from] || from; 30363 to = LengthUnit.aliases[to] || to; 30364 var fromRow = LengthUnit.ratios[from]; 30365 var toRow = LengthUnit.ratios[to]; 30366 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30367 return undefined; 30368 } 30369 return length * fromRow[toRow[0]]; 30370 }; 30371 30372 /** 30373 * @private 30374 * @static 30375 */ 30376 LengthUnit.getMeasures = function () { 30377 var ret = []; 30378 for (var m in LengthUnit.ratios) { 30379 ret.push(m); 30380 } 30381 return ret; 30382 }; 30383 30384 //register with the factory method 30385 Measurement._constructors["length"] = LengthUnit; 30386 30387 30388 /*< MassUnit.js */ 30389 /* 30390 * MassUnit.js - Unit conversions for Mass/mass 30391 * 30392 * Copyright © 2014-2015, JEDLSoft 30393 * 30394 * Licensed under the Apache License, Version 2.0 (the "License"); 30395 * you may not use this file except in compliance with the License. 30396 * You may obtain a copy of the License at 30397 * 30398 * http://www.apache.org/licenses/LICENSE-2.0 30399 * 30400 * Unless required by applicable law or agreed to in writing, software 30401 * distributed under the License is distributed on an "AS IS" BASIS, 30402 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30403 * 30404 * See the License for the specific language governing permissions and 30405 * limitations under the License. 30406 */ 30407 30408 /* 30409 !depends 30410 Measurement.js 30411 */ 30412 30413 30414 /** 30415 * @class 30416 * Create a new mass measurement instance. 30417 * 30418 * @constructor 30419 * @extends Measurement 30420 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30421 * the construction of this instance 30422 */ 30423 var MassUnit = function (options) { 30424 this.unit = "gram"; 30425 this.amount = 0; 30426 this.aliases = MassUnit.aliases; // share this table in all instances 30427 30428 if (options) { 30429 if (typeof(options.unit) !== 'undefined') { 30430 this.originalUnit = options.unit; 30431 this.unit = this.aliases[options.unit] || options.unit; 30432 } 30433 30434 if (typeof(options.amount) === 'object') { 30435 if (options.amount.getMeasure() === "mass") { 30436 this.amount = MassUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30437 } else { 30438 throw "Cannot convert units " + options.amount.unit + " to a mass"; 30439 } 30440 } else if (typeof(options.amount) !== 'undefined') { 30441 this.amount = parseFloat(options.amount); 30442 } 30443 } 30444 30445 if (typeof(MassUnit.ratios[this.unit]) === 'undefined') { 30446 throw "Unknown unit: " + options.unit; 30447 } 30448 }; 30449 30450 MassUnit.prototype = new Measurement(); 30451 MassUnit.prototype.parent = Measurement; 30452 MassUnit.prototype.constructor = MassUnit; 30453 30454 MassUnit.ratios = { 30455 /* index µg mg g oz lp kg st sh ton mt ton ln ton */ 30456 "microgram": [ 1, 1, 0.001, 1e-6, 3.5274e-8, 2.2046e-9, 1e-9, 1.5747e-10, 1.1023e-12, 1e-12, 9.8421e-13 ], 30457 "milligram": [ 2, 1000, 1, 0.001, 3.5274e-5, 2.2046e-6, 1e-6, 1.5747e-7, 1.1023e-9, 1e-9, 9.8421e-10 ], 30458 "gram": [ 3, 1e+6, 1000, 1, 0.035274, 0.00220462, 0.001, 0.000157473, 1.1023e-6, 1e-6, 9.8421e-7 ], 30459 "ounce": [ 4, 2.835e+7, 28349.5, 28.3495, 1, 0.0625, 0.0283495, 0.00446429, 3.125e-5, 2.835e-5, 2.7902e-5 ], 30460 "pound": [ 5, 4.536e+8, 453592, 453.592, 16, 1, 0.453592, 0.0714286, 0.0005, 0.000453592, 0.000446429 ], 30461 "kilogram": [ 6, 1e+9, 1e+6, 1000, 35.274, 2.20462, 1, 0.157473, 0.00110231, 0.001, 0.000984207 ], 30462 "stone": [ 7, 6.35e+9, 6.35e+6, 6350.29, 224, 14, 6.35029, 1, 0.007, 0.00635029, 0.00625 ], 30463 "short ton": [ 8, 9.072e+11, 9.072e+8, 907185, 32000, 2000, 907.185, 142.857, 1, 0.907185, 0.892857 ], 30464 "metric ton": [ 9, 1e+12, 1e+9, 1e+6, 35274, 2204.62, 1000, 157.473, 1.10231, 1, 0.984207 ], 30465 "long ton": [ 10, 1.016e+12, 1.016e+9, 1.016e+6, 35840, 2240, 1016.05, 160, 1.12, 1.01605, 1 ] 30466 }; 30467 30468 MassUnit.metricSystem = { 30469 "microgram": 1, 30470 "milligram": 2, 30471 "gram": 3, 30472 "kilogram": 6, 30473 "metric ton": 9 30474 }; 30475 MassUnit.imperialSystem = { 30476 "ounce": 4, 30477 "pound": 5, 30478 "stone": 7, 30479 "long ton": 10 30480 }; 30481 MassUnit.uscustomarySystem = { 30482 "ounce": 4, 30483 "pound": 5, 30484 "short ton": 8 30485 }; 30486 30487 MassUnit.metricToUScustomary = { 30488 "microgram": "ounce", 30489 "milligram": "ounce", 30490 "gram": "ounce", 30491 "kilogram": "pound", 30492 "metric ton": "long ton" 30493 }; 30494 MassUnit.metricToImperial = { 30495 "microgram": "ounce", 30496 "milligram": "ounce", 30497 "gram": "ounce", 30498 "kilogram": "pound", 30499 "metric ton": "short ton" 30500 }; 30501 30502 MassUnit.imperialToMetric = { 30503 "ounce": "gram", 30504 "pound": "kilogram", 30505 "stone": "kilogram", 30506 "short ton": "metric ton" 30507 }; 30508 MassUnit.imperialToUScustomary = { 30509 "ounce": "ounce", 30510 "pound": "pound", 30511 "stone": "stone", 30512 "short ton": "long ton" 30513 }; 30514 30515 MassUnit.uScustomaryToImperial = { 30516 "ounce": "ounce", 30517 "pound": "pound", 30518 "stone": "stone", 30519 "long ton": "short ton" 30520 }; 30521 MassUnit.uScustomarylToMetric = { 30522 "ounce": "gram", 30523 "pound": "kilogram", 30524 "stone": "kilogram", 30525 "long ton": "metric ton" 30526 }; 30527 30528 /** 30529 * Localize the measurement to the commonly used measurement in that locale. For example 30530 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30531 * the formatted number should be automatically converted to the most appropriate 30532 * measure in the other system, in this case, mph. The formatted result should 30533 * appear as "37.3 mph". 30534 * 30535 * @param {string} locale current locale string 30536 * @returns {Measurement} a new instance that is converted to locale 30537 */ 30538 MassUnit.prototype.localize = function(locale) { 30539 var to; 30540 if (locale === "en-US") { 30541 to = MassUnit.metricToUScustomary[this.unit] || 30542 MassUnit.imperialToUScustomary[this.unit] || this.unit; 30543 } else if (locale === "en-GB") { 30544 to = MassUnit.metricToImperial[this.unit] || 30545 MassUnit.uScustomaryToImperial[this.unit] || this.unit; 30546 } else { 30547 to = MassUnit.uScustomarylToMetric[this.unit] || 30548 MassUnit.imperialToUScustomary[this.unit] || this.unit; 30549 } 30550 return new MassUnit({ 30551 unit: to, 30552 amount: this 30553 }); 30554 }; 30555 30556 /** 30557 * Return the type of this measurement. Examples are "mass", 30558 * "length", "speed", etc. Measurements can only be converted 30559 * to measurements of the same type.<p> 30560 * 30561 * The type of the units is determined automatically from the 30562 * units. For example, the unit "grams" is type "mass". Use the 30563 * static call {@link Measurement.getAvailableUnits} 30564 * to find out what units this version of ilib supports. 30565 * 30566 * @return {string} the name of the type of this measurement 30567 */ 30568 MassUnit.prototype.getMeasure = function() { 30569 return "mass"; 30570 }; 30571 30572 /** 30573 * Return a new measurement instance that is converted to a new 30574 * measurement unit. Measurements can only be converted 30575 * to measurements of the same type.<p> 30576 * 30577 * @param {string} to The name of the units to convert to 30578 * @return {Measurement|undefined} the converted measurement 30579 * or undefined if the requested units are for a different 30580 * measurement type 30581 */ 30582 MassUnit.prototype.convert = function(to) { 30583 if (!to || typeof(MassUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30584 return undefined; 30585 } 30586 return new MassUnit({ 30587 unit: to, 30588 amount: this 30589 }); 30590 }; 30591 30592 MassUnit.aliases = { 30593 "µg":"microgram", 30594 "microgram":"microgram", 30595 "mcg":"microgram", 30596 "milligram":"milligram", 30597 "mg":"milligram", 30598 "milligrams":"milligram", 30599 "Milligram":"milligram", 30600 "Milligrams":"milligram", 30601 "MilliGram":"milligram", 30602 "MilliGrams":"milligram", 30603 "g":"gram", 30604 "gram":"gram", 30605 "grams":"gram", 30606 "Gram":"gram", 30607 "Grams":"gram", 30608 "ounce":"ounce", 30609 "oz":"ounce", 30610 "Ounce":"ounce", 30611 "℥":"ounce", 30612 "pound":"pound", 30613 "poundm":"pound", 30614 "℔":"pound", 30615 "lb":"pound", 30616 "pounds":"pound", 30617 "Pound":"pound", 30618 "Pounds":"pound", 30619 "kilogram":"kilogram", 30620 "kg":"kilogram", 30621 "kilograms":"kilogram", 30622 "kilo grams":"kilogram", 30623 "kilo gram":"kilogram", 30624 "Kilogram":"kilogram", 30625 "Kilograms":"kilogram", 30626 "KiloGram":"kilogram", 30627 "KiloGrams":"kilogram", 30628 "Kilo gram":"kilogram", 30629 "Kilo grams":"kilogram", 30630 "Kilo Gram":"kilogram", 30631 "Kilo Grams":"kilogram", 30632 "stone":"stone", 30633 "st":"stone", 30634 "stones":"stone", 30635 "Stone":"stone", 30636 "short ton":"short ton", 30637 "Short ton":"short ton", 30638 "Short Ton":"short ton", 30639 "metric ton":"metric ton", 30640 "metricton":"metric ton", 30641 "t":"metric ton", 30642 "tonne":"metric ton", 30643 "Tonne":"metric ton", 30644 "Metric Ton":"metric ton", 30645 "MetricTon":"metric ton", 30646 "long ton":"long ton", 30647 "longton":"long ton", 30648 "Longton":"long ton", 30649 "Long ton":"long ton", 30650 "Long Ton":"long ton", 30651 "ton":"long ton", 30652 "Ton":"long ton" 30653 }; 30654 30655 /** 30656 * Convert a mass to another measure. 30657 * @static 30658 * @param to {string} unit to convert to 30659 * @param from {string} unit to convert from 30660 * @param mass {number} amount to be convert 30661 * @returns {number|undefined} the converted amount 30662 */ 30663 MassUnit.convert = function(to, from, mass) { 30664 from = MassUnit.aliases[from] || from; 30665 to = MassUnit.aliases[to] || to; 30666 var fromRow = MassUnit.ratios[from]; 30667 var toRow = MassUnit.ratios[to]; 30668 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30669 return undefined; 30670 } 30671 return mass * fromRow[toRow[0]]; 30672 }; 30673 30674 /** 30675 * Scale the measurement unit to an acceptable level. The scaling 30676 * happens so that the integer part of the amount is as small as 30677 * possible without being below zero. This will result in the 30678 * largest units that can represent this measurement without 30679 * fractions. Measurements can only be scaled to other measurements 30680 * of the same type. 30681 * 30682 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30683 * or undefined if the system can be inferred from the current measure 30684 * @return {Measurement} a new instance that is scaled to the 30685 * right level 30686 */ 30687 MassUnit.prototype.scale = function(measurementsystem) { 30688 var mSystem; 30689 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 30690 && typeof(MassUnit.metricSystem[this.unit]) !== 'undefined')) { 30691 mSystem = MassUnit.metricSystem; 30692 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 30693 && typeof(MassUnit.imperialSystem[this.unit]) !== 'undefined')) { 30694 mSystem = MassUnit.imperialSystem; 30695 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 30696 && typeof(MassUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 30697 mSystem = MassUnit.uscustomarySystem; 30698 } else { 30699 return new MassUnit({ 30700 unit: this.unit, 30701 amount: this.amount 30702 }); 30703 } 30704 30705 var mass = this.amount; 30706 var munit = this.amount; 30707 var fromRow = MassUnit.ratios[this.unit]; 30708 30709 mass = 18446744073709551999; 30710 30711 for (var m in mSystem) { 30712 var tmp = this.amount * fromRow[mSystem[m]]; 30713 if (tmp >= 1 && tmp < mass) { 30714 mass = tmp; 30715 munit = m; 30716 } 30717 } 30718 30719 return new MassUnit({ 30720 unit: munit, 30721 amount: mass 30722 }); 30723 }; 30724 30725 /** 30726 * @private 30727 * @static 30728 */ 30729 MassUnit.getMeasures = function () { 30730 var ret = []; 30731 for (var m in MassUnit.ratios) { 30732 ret.push(m); 30733 } 30734 return ret; 30735 }; 30736 30737 //register with the factory method 30738 Measurement._constructors["mass"] = MassUnit; 30739 30740 30741 /*< TemperatureUnit.js */ 30742 /* 30743 * temperature.js - Unit conversions for Temperature/temperature 30744 * 30745 * Copyright © 2014-2015, JEDLSoft 30746 * 30747 * Licensed under the Apache License, Version 2.0 (the "License"); 30748 * you may not use this file except in compliance with the License. 30749 * You may obtain a copy of the License at 30750 * 30751 * http://www.apache.org/licenses/LICENSE-2.0 30752 * 30753 * Unless required by applicable law or agreed to in writing, software 30754 * distributed under the License is distributed on an "AS IS" BASIS, 30755 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30756 * 30757 * See the License for the specific language governing permissions and 30758 * limitations under the License. 30759 */ 30760 30761 /* 30762 !depends 30763 Measurement.js 30764 */ 30765 30766 30767 /** 30768 * @class 30769 * Create a new Temperature measurement instance. 30770 * 30771 * @constructor 30772 * @extends Measurement 30773 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30774 * the construction of this instance 30775 */ 30776 var TemperatureUnit = function (options) { 30777 this.unit = "celsius"; 30778 this.amount = 0; 30779 this.aliases = TemperatureUnit.aliases; // share this table in all instances 30780 30781 if (options) { 30782 if (typeof(options.unit) !== 'undefined') { 30783 this.originalUnit = options.unit; 30784 this.unit = this.aliases[options.unit] || options.unit; 30785 } 30786 30787 if (typeof(options.amount) === 'object') { 30788 if (options.amount.getMeasure() === "temperature") { 30789 this.amount = TemperatureUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30790 } else { 30791 throw "Cannot convert unit " + options.amount.unit + " to a temperature"; 30792 } 30793 } else if (typeof(options.amount) !== 'undefined') { 30794 this.amount = parseFloat(options.amount); 30795 } 30796 } 30797 }; 30798 30799 TemperatureUnit.prototype = new Measurement(); 30800 TemperatureUnit.prototype.parent = Measurement; 30801 TemperatureUnit.prototype.constructor = TemperatureUnit; 30802 30803 /** 30804 * Return the type of this measurement. Examples are "mass", 30805 * "length", "speed", etc. Measurements can only be converted 30806 * to measurements of the same type.<p> 30807 * 30808 * The type of the units is determined automatically from the 30809 * units. For example, the unit "grams" is type "mass". Use the 30810 * static call {@link Measurement.getAvailableUnits} 30811 * to find out what units this version of ilib supports. 30812 * 30813 * @return {string} the name of the type of this measurement 30814 */ 30815 TemperatureUnit.prototype.getMeasure = function() { 30816 return "temperature"; 30817 }; 30818 30819 TemperatureUnit.aliases = { 30820 "Celsius": "celsius", 30821 "celsius": "celsius", 30822 "C": "celsius", 30823 "centegrade": "celsius", 30824 "Centegrade": "celsius", 30825 "centigrade": "celsius", 30826 "Centigrade": "celsius", 30827 "fahrenheit": "fahrenheit", 30828 "Fahrenheit": "fahrenheit", 30829 "F": "fahrenheit", 30830 "kelvin": "kelvin", 30831 "K": "kelvin", 30832 "Kelvin": "kelvin", 30833 "°F": "fahrenheit", 30834 "℉": "fahrenheit", 30835 "℃": "celsius", 30836 "°C": "celsius" 30837 }; 30838 30839 /** 30840 * Return a new measurement instance that is converted to a new 30841 * measurement unit. Measurements can only be converted 30842 * to measurements of the same type.<p> 30843 * 30844 * @param {string} to The name of the units to convert to 30845 * @return {Measurement|undefined} the converted measurement 30846 * or undefined if the requested units are for a different 30847 * measurement type 30848 */ 30849 TemperatureUnit.prototype.convert = function(to) { 30850 if (!to || typeof(TemperatureUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30851 return undefined; 30852 } 30853 return new TemperatureUnit({ 30854 unit: to, 30855 amount: this 30856 }); 30857 }; 30858 30859 /** 30860 * Convert a temperature to another measure. 30861 * @static 30862 * @param to {string} unit to convert to 30863 * @param from {string} unit to convert from 30864 * @param temperature {number} amount to be convert 30865 * @returns {number|undefined} the converted amount 30866 */ 30867 TemperatureUnit.convert = function(to, from, temperature) { 30868 var result = 0; 30869 from = TemperatureUnit.aliases[from] || from; 30870 to = TemperatureUnit.aliases[to] || to; 30871 if (from === to) 30872 return temperature; 30873 30874 else if (from === "celsius") { 30875 if (to === "fahrenheit") { 30876 result = ((temperature * 9 / 5) + 32); 30877 } else if (to === "kelvin") { 30878 result = (temperature + 273.15); 30879 } 30880 30881 } else if (from === "fahrenheit") { 30882 if (to === "celsius") { 30883 result = ((5 / 9 * (temperature - 32))); 30884 } else if (to === "kelvin") { 30885 result = ((temperature + 459.67) * 5 / 9); 30886 } 30887 } else if (from === "kelvin") { 30888 if (to === "celsius") { 30889 result = (temperature - 273.15); 30890 } else if (to === "fahrenheit") { 30891 result = ((temperature * 9 / 5) - 459.67); 30892 } 30893 } 30894 30895 return result; 30896 }; 30897 30898 /** 30899 * Scale the measurement unit to an acceptable level. The scaling 30900 * happens so that the integer part of the amount is as small as 30901 * possible without being below zero. This will result in the 30902 * largest units that can represent this measurement without 30903 * fractions. Measurements can only be scaled to other measurements 30904 * of the same type. 30905 * 30906 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30907 * or undefined if the system can be inferred from the current measure 30908 * @return {Measurement} a new instance that is scaled to the 30909 * right level 30910 */ 30911 TemperatureUnit.prototype.scale = function(measurementsystem) { 30912 return new TemperatureUnit({ 30913 unit: this.unit, 30914 amount: this.amount 30915 }); 30916 }; 30917 30918 /** 30919 * @private 30920 * @static 30921 */ 30922 TemperatureUnit.getMeasures = function () { 30923 return ["celsius", "kelvin", "fahrenheit"]; 30924 }; 30925 TemperatureUnit.metricToUScustomary = { 30926 "celsius": "fahrenheit" 30927 }; 30928 TemperatureUnit.usCustomaryToMetric = { 30929 "fahrenheit": "celsius" 30930 }; 30931 30932 /** 30933 * Localize the measurement to the commonly used measurement in that locale. For example 30934 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30935 * the formatted number should be automatically converted to the most appropriate 30936 * measure in the other system, in this case, mph. The formatted result should 30937 * appear as "37.3 mph". 30938 * 30939 * @param {string} locale current locale string 30940 * @returns {Measurement} a new instance that is converted to locale 30941 */ 30942 TemperatureUnit.prototype.localize = function(locale) { 30943 var to; 30944 if (locale === "en-US" ) { 30945 to = TemperatureUnit.metricToUScustomary[this.unit] || this.unit; 30946 } else { 30947 to = TemperatureUnit.usCustomaryToMetric[this.unit] || this.unit; 30948 } 30949 return new TemperatureUnit({ 30950 unit: to, 30951 amount: this 30952 }); 30953 }; 30954 //register with the factory method 30955 Measurement._constructors["temperature"] = TemperatureUnit; 30956 30957 30958 /*< TimeUnit.js */ 30959 /* 30960 * Time.js - Unit conversions for Times/times 30961 * 30962 * Copyright © 2014-2015, JEDLSoft 30963 * 30964 * Licensed under the Apache License, Version 2.0 (the "License"); 30965 * you may not use this file except in compliance with the License. 30966 * You may obtain a copy of the License at 30967 * 30968 * http://www.apache.org/licenses/LICENSE-2.0 30969 * 30970 * Unless required by applicable law or agreed to in writing, software 30971 * distributed under the License is distributed on an "AS IS" BASIS, 30972 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30973 * 30974 * See the License for the specific language governing permissions and 30975 * limitations under the License. 30976 */ 30977 30978 /* 30979 !depends 30980 Measurement.js 30981 */ 30982 30983 30984 /** 30985 * @class 30986 * Create a new time measurement instance. 30987 * 30988 * @constructor 30989 * @extends Measurement 30990 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30991 * the construction of this instance 30992 */ 30993 var TimeUnit = function (options) { 30994 this.unit = "second"; 30995 this.amount = 0; 30996 this.aliases = TimeUnit.aliases; // share this table in all instances 30997 30998 if (options) { 30999 if (typeof(options.unit) !== 'undefined') { 31000 this.originalUnit = options.unit; 31001 this.unit = this.aliases[options.unit] || options.unit; 31002 } 31003 31004 if (typeof(options.amount) === 'object') { 31005 if (options.amount.getMeasure() === "time") { 31006 this.amount = TimeUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31007 } else { 31008 throw "Cannot convert units " + options.amount.unit + " to a time"; 31009 } 31010 } else if (typeof(options.amount) !== 'undefined') { 31011 this.amount = parseFloat(options.amount); 31012 } 31013 } 31014 31015 if (typeof(TimeUnit.ratios[this.unit]) === 'undefined') { 31016 throw "Unknown unit: " + options.unit; 31017 } 31018 }; 31019 31020 TimeUnit.prototype = new Measurement(); 31021 TimeUnit.prototype.parent = Measurement; 31022 TimeUnit.prototype.constructor = TimeUnit; 31023 31024 TimeUnit.ratios = { 31025 /* index nsec msec mlsec sec min hour day week month year decade century */ 31026 "nanosecond": [ 1, 1, 0.001, 1e-6, 1e-9, 1.6667e-11, 2.7778e-13, 1.1574e-14, 1.6534e-15, 3.8027e-16, 3.1689e-17, 3.1689e-18, 3.1689e-19 ], 31027 "microsecond": [ 2, 1000, 1, 0.001, 1e-6, 1.6667e-8, 2.7778e-10, 1.1574e-11, 1.6534e-12, 3.8027e-13, 3.1689e-14, 3.1689e-15, 3.1689e-16 ], 31028 "millisecond": [ 3, 1e+6, 1000, 1, 0.001, 1.6667e-5, 2.7778e-7, 1.1574e-8, 1.6534e-9, 3.8027e-10, 3.1689e-11, 3.1689e-12, 3.1689e-13 ], 31029 "second": [ 4, 1e+9, 1e+6, 1000, 1, 0.0166667, 0.000277778, 1.1574e-5, 1.6534e-6, 3.8027e-7, 3.1689e-8, 3.1689e-9, 3.1689e-10 ], 31030 "minute": [ 5, 6e+10, 6e+7, 60000, 60, 1, 0.0166667, 0.000694444, 9.9206e-5, 2.2816e-5, 1.9013e-6, 1.9013e-7, 1.9013e-8 ], 31031 "hour": [ 6, 3.6e+12, 3.6e+9, 3.6e+6, 3600, 60, 1, 0.0416667, 0.00595238, 0.00136895, 0.00011408, 1.1408e-5, 1.1408e-6 ], 31032 "day": [ 7, 8.64e+13, 8.64e+10, 8.64e+7, 86400, 1440, 24, 1, 0.142857, 0.0328549, 0.00273791, 0.000273791, 2.7379e-5 ], 31033 "week": [ 8, 6.048e+14, 6.048e+11, 6.048e+8, 604800, 10080, 168, 7, 1, 0.229984, 0.0191654, 0.00191654, 0.000191654 ], 31034 "month": [ 9, 2.63e+15, 2.63e+12, 2.63e+9, 2.63e+6, 43829.1, 730.484, 30.4368, 4.34812, 1, 0.0833333, 0.00833333, 0.000833333 ], 31035 "year": [ 10, 3.156e+16, 3.156e+13, 3.156e+10, 3.156e+7, 525949, 8765.81, 365.242, 52.1775, 12, 1, 0.1, 0.01 ], 31036 "decade": [ 11, 3.156e+17, 3.156e+14, 3.156e+11, 3.156e+8, 5.259e+6, 87658.1, 3652.42, 521.775, 120, 10, 1, 0.1 ], 31037 "century": [ 12, 3.156e+18, 3.156e+18, 3.156e+12, 3.156e+9, 5.259e+7, 876581, 36524.2, 5217.75, 1200, 100, 10, 1 ] 31038 }; 31039 31040 /** 31041 * Return the type of this measurement. Examples are "mass", 31042 * "length", "speed", etc. Measurements can only be converted 31043 * to measurements of the same type.<p> 31044 * 31045 * The type of the units is determined automatically from the 31046 * units. For example, the unit "grams" is type "mass". Use the 31047 * static call {@link Measurement.getAvailableUnits} 31048 * to find out what units this version of ilib supports. 31049 * 31050 * @return {string} the name of the type of this measurement 31051 */ 31052 TimeUnit.prototype.getMeasure = function() { 31053 return "time"; 31054 }; 31055 31056 /** 31057 * Return a new measurement instance that is converted to a new 31058 * measurement unit. Measurements can only be converted 31059 * to measurements of the same type.<p> 31060 * 31061 * @param {string} to The name of the units to convert to 31062 * @return {Measurement|undefined} the converted measurement 31063 * or undefined if the requested units are for a different 31064 * measurement type 31065 */ 31066 TimeUnit.prototype.convert = function(to) { 31067 if (!to || typeof(TimeUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31068 return undefined; 31069 } 31070 return new TimeUnit({ 31071 unit: to, 31072 amount: this 31073 }); 31074 }; 31075 31076 TimeUnit.aliases = { 31077 "ns": "nanosecond", 31078 "NS": "nanosecond", 31079 "nS": "nanosecond", 31080 "Ns": "nanosecond", 31081 "Nanosecond": "nanosecond", 31082 "Nanoseconds": "nanosecond", 31083 "nanosecond": "nanosecond", 31084 "nanoseconds": "nanosecond", 31085 "NanoSecond": "nanosecond", 31086 "NanoSeconds": "nanosecond", 31087 "μs": "microsecond", 31088 "μS": "microsecond", 31089 "microsecond": "microsecond", 31090 "microseconds": "microsecond", 31091 "Microsecond": "microsecond", 31092 "Microseconds": "microsecond", 31093 "MicroSecond": "microsecond", 31094 "MicroSeconds": "microsecond", 31095 "ms": "millisecond", 31096 "MS": "millisecond", 31097 "mS": "millisecond", 31098 "Ms": "millisecond", 31099 "millisecond": "millisecond", 31100 "milliseconds": "millisecond", 31101 "Millisecond": "millisecond", 31102 "Milliseconds": "millisecond", 31103 "MilliSecond": "millisecond", 31104 "MilliSeconds": "millisecond", 31105 "s": "second", 31106 "S": "second", 31107 "sec": "second", 31108 "second": "second", 31109 "seconds": "second", 31110 "Second": "second", 31111 "Seconds": "second", 31112 "min": "minute", 31113 "Min": "minute", 31114 "minute": "minute", 31115 "minutes": "minute", 31116 "Minute": "minute", 31117 "Minutes": "minute", 31118 "h": "hour", 31119 "H": "hour", 31120 "hr": "hour", 31121 "Hr": "hour", 31122 "hR": "hour", 31123 "HR": "hour", 31124 "hour": "hour", 31125 "hours": "hour", 31126 "Hour": "hour", 31127 "Hours": "hour", 31128 "Hrs": "hour", 31129 "hrs": "hour", 31130 "day": "day", 31131 "days": "day", 31132 "Day": "day", 31133 "Days": "day", 31134 "week": "week", 31135 "weeks": "week", 31136 "Week": "week", 31137 "Weeks": "week", 31138 "month": "month", 31139 "Month": "month", 31140 "months": "month", 31141 "Months": "month", 31142 "year": "year", 31143 "years": "year", 31144 "Year": "year", 31145 "Years": "year", 31146 "yr": "year", 31147 "Yr": "year", 31148 "yrs": "year", 31149 "Yrs": "year", 31150 "decade": "decade", 31151 "decades": "decade", 31152 "Decade": "decade", 31153 "Decades": "decade", 31154 "century": "century", 31155 "centuries": "century", 31156 "Century": "century", 31157 "Centuries": "century" 31158 }; 31159 31160 /** 31161 * Convert a time to another measure. 31162 * @static 31163 * @param to {string} unit to convert to 31164 * @param from {string} unit to convert from 31165 * @param time {number} amount to be convert 31166 * @returns {number|undefined} the converted amount 31167 */ 31168 TimeUnit.convert = function(to, from, time) { 31169 from = TimeUnit.aliases[from] || from; 31170 to = TimeUnit.aliases[to] || to; 31171 var fromRow = TimeUnit.ratios[from]; 31172 var toRow = TimeUnit.ratios[to]; 31173 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31174 return undefined; 31175 } 31176 return time * fromRow[toRow[0]]; 31177 }; 31178 31179 /** 31180 * Localize the measurement to the commonly used measurement in that locale. For example 31181 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31182 * the formatted number should be automatically converted to the most appropriate 31183 * measure in the other system, in this case, mph. The formatted result should 31184 * appear as "37.3 mph". 31185 * 31186 * @param {string} locale current locale string 31187 * @returns {Measurement} a new instance that is converted to locale 31188 */ 31189 TimeUnit.prototype.localize = function(locale) { 31190 return new TimeUnit({ 31191 unit: this.unit, 31192 amount: this.amount 31193 }); 31194 }; 31195 31196 /** 31197 * Scale the measurement unit to an acceptable level. The scaling 31198 * happens so that the integer part of the amount is as small as 31199 * possible without being below zero. This will result in the 31200 * largest units that can represent this measurement without 31201 * fractions. Measurements can only be scaled to other measurements 31202 * of the same type. 31203 * 31204 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31205 * or undefined if the system can be inferred from the current measure 31206 * @return {Measurement} a new instance that is scaled to the 31207 * right level 31208 */ 31209 TimeUnit.prototype.scale = function(measurementsystem) { 31210 31211 var fromRow = TimeUnit.ratios[this.unit]; 31212 var time = this.amount; 31213 var munit = this.unit; 31214 var i; 31215 31216 time = 18446744073709551999; 31217 for (var m in TimeUnit.ratios) { 31218 i = TimeUnit.ratios[m][0]; 31219 var tmp = this.amount * fromRow[i]; 31220 if (tmp >= 1 && tmp < time) { 31221 time = tmp; 31222 munit = m; 31223 } 31224 } 31225 31226 return new TimeUnit({ 31227 unit: munit, 31228 amount: time 31229 }); 31230 }; 31231 /** 31232 * @private 31233 * @static 31234 */ 31235 TimeUnit.getMeasures = function () { 31236 var ret = []; 31237 for (var m in TimeUnit.ratios) { 31238 ret.push(m); 31239 } 31240 return ret; 31241 }; 31242 31243 //register with the factory method 31244 Measurement._constructors["time"] = TimeUnit; 31245 31246 31247 /*< VelocityUnit.js */ 31248 /* 31249 * VelocityUnit.js - Unit conversions for VelocityUnits/speeds 31250 * 31251 * Copyright © 2014-2015, JEDLSoft 31252 * 31253 * Licensed under the Apache License, Version 2.0 (the "License"); 31254 * you may not use this file except in compliance with the License. 31255 * You may obtain a copy of the License at 31256 * 31257 * http://www.apache.org/licenses/LICENSE-2.0 31258 * 31259 * Unless required by applicable law or agreed to in writing, software 31260 * distributed under the License is distributed on an "AS IS" BASIS, 31261 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31262 * 31263 * See the License for the specific language governing permissions and 31264 * limitations under the License. 31265 */ 31266 31267 /* 31268 !depends 31269 Measurement.js 31270 */ 31271 31272 31273 /** 31274 * @class 31275 * Create a new speed measurement instance. 31276 * 31277 * @constructor 31278 * @extends Measurement 31279 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31280 * the construction of this instance 31281 */ 31282 var VelocityUnit = function (options) { 31283 this.unit = "meters/second"; 31284 this.amount = 0; 31285 this.aliases = VelocityUnit.aliases; // share this table in all instances 31286 31287 if (options) { 31288 if (typeof(options.unit) !== 'undefined') { 31289 this.originalUnit = options.unit; 31290 this.unit = this.aliases[options.unit] || options.unit; 31291 } 31292 31293 if (typeof(options.amount) === 'object') { 31294 if (options.amount.getMeasure() === "speed") { 31295 this.amount = VelocityUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31296 } else { 31297 throw "Cannot convert units " + options.amount.unit + " to a speed"; 31298 } 31299 } else if (typeof(options.amount) !== 'undefined') { 31300 this.amount = parseFloat(options.amount); 31301 } 31302 } 31303 31304 if (typeof(VelocityUnit.ratios[this.unit]) === 'undefined') { 31305 throw "Unknown unit: " + options.unit; 31306 } 31307 }; 31308 31309 VelocityUnit.prototype = new Measurement(); 31310 VelocityUnit.prototype.parent = Measurement; 31311 VelocityUnit.prototype.constructor = VelocityUnit; 31312 31313 VelocityUnit.ratios = { 31314 /* index, k/h f/s miles/h knot m/s km/s miles/s */ 31315 "kilometer/hour": [ 1, 1, 0.911344, 0.621371, 0.539957, 0.277778, 2.77778e-4, 1.72603109e-4 ], 31316 "feet/second": [ 2, 1.09728, 1, 0.681818, 0.592484, 0.3048, 3.048e-4, 1.89393939e-4 ], 31317 "miles/hour": [ 3, 1.60934, 1.46667, 1, 0.868976, 0.44704, 4.4704e-4, 2.77777778e-4 ], 31318 "knot": [ 4, 1.852, 1.68781, 1.15078, 1, 0.514444, 5.14444e-4, 3.19660958e-4 ], 31319 "meters/second": [ 5, 3.6, 3.28084, 2.236936, 1.94384, 1, 0.001, 6.21371192e-4 ], 31320 "kilometer/second": [ 6, 3600, 3280.8399, 2236.93629, 1943.84449, 1000, 1, 0.621371192 ], 31321 "miles/second": [ 7, 5793.6384, 5280, 3600, 3128.31447, 1609.344, 1.609344, 1 ] 31322 }; 31323 31324 VelocityUnit.metricSystem = { 31325 "kilometer/hour": 1, 31326 "meters/second": 5, 31327 "kilometer/second": 6 31328 }; 31329 VelocityUnit.imperialSystem = { 31330 "feet/second": 2, 31331 "miles/hour": 3, 31332 "knot": 4, 31333 "miles/second": 7 31334 }; 31335 VelocityUnit.uscustomarySystem = { 31336 "feet/second": 2, 31337 "miles/hour": 3, 31338 "knot": 4, 31339 "miles/second": 7 31340 }; 31341 31342 VelocityUnit.metricToUScustomary = { 31343 "kilometer/hour": "miles/hour", 31344 "meters/second": "feet/second", 31345 "kilometer/second": "miles/second" 31346 }; 31347 VelocityUnit.UScustomaryTometric = { 31348 "miles/hour": "kilometer/hour", 31349 "feet/second": "meters/second", 31350 "miles/second": "kilometer/second", 31351 "knot": "kilometer/hour" 31352 }; 31353 31354 /** 31355 * Return the type of this measurement. Examples are "mass", 31356 * "length", "speed", etc. Measurements can only be converted 31357 * to measurements of the same type.<p> 31358 * 31359 * The type of the units is determined automatically from the 31360 * units. For example, the unit "grams" is type "mass". Use the 31361 * static call {@link Measurement.getAvailableUnits} 31362 * to find out what units this version of ilib supports. 31363 * 31364 * @return {string} the name of the type of this measurement 31365 */ 31366 VelocityUnit.prototype.getMeasure = function() { 31367 return "speed"; 31368 }; 31369 31370 /** 31371 * Return a new measurement instance that is converted to a new 31372 * measurement unit. Measurements can only be converted 31373 * to measurements of the same type.<p> 31374 * 31375 * @param {string} to The name of the units to convert to 31376 * @return {Measurement|undefined} the converted measurement 31377 * or undefined if the requested units are for a different 31378 * measurement type 31379 */ 31380 VelocityUnit.prototype.convert = function(to) { 31381 if (!to || typeof(VelocityUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31382 return undefined; 31383 } 31384 return new VelocityUnit({ 31385 unit: to, 31386 amount: this 31387 }); 31388 }; 31389 31390 /** 31391 * Scale the measurement unit to an acceptable level. The scaling 31392 * happens so that the integer part of the amount is as small as 31393 * possible without being below zero. This will result in the 31394 * largest units that can represent this measurement without 31395 * fractions. Measurements can only be scaled to other measurements 31396 * of the same type. 31397 * 31398 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31399 * or undefined if the system can be inferred from the current measure 31400 * @return {Measurement} a new instance that is scaled to the 31401 * right level 31402 */ 31403 VelocityUnit.prototype.scale = function(measurementsystem) { 31404 var mSystem; 31405 if (measurementsystem === "metric" || 31406 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.metricSystem[this.unit]) !== 'undefined')) { 31407 mSystem = VelocityUnit.metricSystem; 31408 } else if (measurementsystem === "imperial" || 31409 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.imperialSystem[this.unit]) !== 'undefined')) { 31410 mSystem = VelocityUnit.imperialSystem; 31411 } else if (measurementsystem === "uscustomary" || 31412 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 31413 mSystem = VelocityUnit.uscustomarySystem; 31414 } else { 31415 return new VelocityUnit({ 31416 unit: this.unit, 31417 amount: this.amount 31418 }); 31419 } 31420 31421 var speed = this.amount; 31422 var munit = this.unit; 31423 var fromRow = VelocityUnit.ratios[this.unit]; 31424 31425 speed = 18446744073709551999; 31426 31427 for ( var m in mSystem) { 31428 var tmp = this.amount * fromRow[mSystem[m]]; 31429 if (tmp >= 1 && tmp < speed) { 31430 speed = tmp; 31431 munit = m; 31432 } 31433 } 31434 31435 return new VelocityUnit({ 31436 unit: munit, 31437 amount: speed 31438 }); 31439 }; 31440 31441 /** 31442 * Localize the measurement to the commonly used measurement in that locale. For example 31443 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31444 * the formatted number should be automatically converted to the most appropriate 31445 * measure in the other system, in this case, mph. The formatted result should 31446 * appear as "37.3 mph". 31447 * 31448 * @param {string} locale current locale string 31449 * @returns {Measurement} a new instance that is converted to locale 31450 */ 31451 VelocityUnit.prototype.localize = function(locale) { 31452 var to; 31453 if (locale === "en-US" || locale === "en-GB") { 31454 to = VelocityUnit.metricToUScustomary[this.unit] || this.unit; 31455 } else { 31456 to = VelocityUnit.UScustomaryTometric[this.unit] || this.unit; 31457 } 31458 return new VelocityUnit({ 31459 unit: to, 31460 amount: this 31461 }); 31462 }; 31463 31464 VelocityUnit.aliases = { 31465 "foot/sec": "feet/second", 31466 "foot/s": "feet/second", 31467 "feet/s": "feet/second", 31468 "f/s": "feet/second", 31469 "feet/second": "feet/second", 31470 "feet/sec": "feet/second", 31471 "meter/sec": "meters/second", 31472 "meter/s": "meters/second", 31473 "meters/s": "meters/second", 31474 "metre/sec": "meters/second", 31475 "metre/s": "meters/second", 31476 "metres/s": "meters/second", 31477 "mt/sec": "meters/second", 31478 "m/sec": "meters/second", 31479 "mt/s": "meters/second", 31480 "m/s": "meters/second", 31481 "mps": "meters/second", 31482 "meters/second": "meters/second", 31483 "meters/sec": "meters/second", 31484 "kilometer/hour": "kilometer/hour", 31485 "km/hour": "kilometer/hour", 31486 "kilometers/hour": "kilometer/hour", 31487 "kmh": "kilometer/hour", 31488 "km/h": "kilometer/hour", 31489 "kilometer/h": "kilometer/hour", 31490 "kilometers/h": "kilometer/hour", 31491 "km/hr": "kilometer/hour", 31492 "kilometer/hr": "kilometer/hour", 31493 "kilometers/hr": "kilometer/hour", 31494 "kilometre/hour": "kilometer/hour", 31495 "mph": "miles/hour", 31496 "mile/hour": "miles/hour", 31497 "mile/hr": "miles/hour", 31498 "mile/h": "miles/hour", 31499 "miles/h": "miles/hour", 31500 "miles/hr": "miles/hour", 31501 "miles/hour": "miles/hour", 31502 "kn": "knot", 31503 "kt": "knot", 31504 "kts": "knot", 31505 "knots": "knot", 31506 "nm/h": "knot", 31507 "nm/hr": "knot", 31508 "nauticalmile/h": "knot", 31509 "nauticalmile/hr": "knot", 31510 "nauticalmile/hour": "knot", 31511 "nauticalmiles/hr": "knot", 31512 "nauticalmiles/hour": "knot", 31513 "knot": "knot", 31514 "kilometer/second": "kilometer/second", 31515 "kilometer/sec": "kilometer/second", 31516 "kilometre/sec": "kilometer/second", 31517 "Kilometre/sec": "kilometer/second", 31518 "kilometers/second": "kilometer/second", 31519 "kilometers/sec": "kilometer/second", 31520 "kilometres/sec": "kilometer/second", 31521 "Kilometres/sec": "kilometer/second", 31522 "km/sec": "kilometer/second", 31523 "Km/s": "kilometer/second", 31524 "km/s": "kilometer/second", 31525 "miles/second": "miles/second", 31526 "miles/sec": "miles/second", 31527 "miles/s": "miles/second", 31528 "mile/s": "miles/second", 31529 "mile/sec": "miles/second", 31530 "Mile/s": "miles/second" 31531 }; 31532 31533 /** 31534 * Convert a speed to another measure. 31535 * @static 31536 * @param to {string} unit to convert to 31537 * @param from {string} unit to convert from 31538 * @param speed {number} amount to be convert 31539 * @returns {number|undefined} the converted amount 31540 */ 31541 VelocityUnit.convert = function(to, from, speed) { 31542 from = VelocityUnit.aliases[from] || from; 31543 to = VelocityUnit.aliases[to] || to; 31544 var fromRow = VelocityUnit.ratios[from]; 31545 var toRow = VelocityUnit.ratios[to]; 31546 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31547 return undefined; 31548 } 31549 var result = speed * fromRow[toRow[0]]; 31550 return result; 31551 }; 31552 31553 /** 31554 * @private 31555 * @static 31556 */ 31557 VelocityUnit.getMeasures = function () { 31558 var ret = []; 31559 for (var m in VelocityUnit.ratios) { 31560 ret.push(m); 31561 } 31562 return ret; 31563 }; 31564 31565 //register with the factory method 31566 Measurement._constructors["speed"] = VelocityUnit; 31567 Measurement._constructors["velocity"] = VelocityUnit; 31568 31569 31570 /*< VolumeUnit.js */ 31571 /* 31572 * volume.js - Unit conversions for volume 31573 * 31574 * Copyright © 2014-2015, JEDLSoft 31575 * 31576 * Licensed under the Apache License, Version 2.0 (the "License"); 31577 * you may not use this file except in compliance with the License. 31578 * You may obtain a copy of the License at 31579 * 31580 * http://www.apache.org/licenses/LICENSE-2.0 31581 * 31582 * 31583 * Unless required by applicable law or agreed to in writing, software 31584 * distributed under the License is distributed on an "AS IS" BASIS, 31585 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31586 * 31587 * See the License for the specific language governing permissions and 31588 * limitations under the License. 31589 */ 31590 31591 /* 31592 !depends 31593 Measurement.js 31594 */ 31595 31596 31597 /** 31598 * @class 31599 * Create a new Volume measurement instance. 31600 * 31601 * @constructor 31602 * @extends Measurement 31603 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31604 * the construction of this instance 31605 */ 31606 var VolumeUnit = function (options) { 31607 this.unit = "cubic meter"; 31608 this.amount = 0; 31609 this.aliases = VolumeUnit.aliases; // share this table in all instances 31610 31611 if (options) { 31612 if (typeof(options.unit) !== 'undefined') { 31613 this.originalUnit = options.unit; 31614 this.unit = this.aliases[options.unit] || options.unit; 31615 } 31616 31617 if (typeof(options.amount) === 'object') { 31618 if (options.amount.getMeasure() === "volume") { 31619 this.amount = VolumeUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31620 } else { 31621 throw "Cannot convert unit " + options.amount.unit + " to a volume"; 31622 } 31623 } else if (typeof(options.amount) !== 'undefined') { 31624 this.amount = parseFloat(options.amount); 31625 } 31626 } 31627 31628 if (typeof(VolumeUnit.ratios[this.unit]) === 'undefined') { 31629 throw "Unknown unit: " + options.unit; 31630 } 31631 }; 31632 31633 VolumeUnit.prototype = new Measurement(); 31634 VolumeUnit.prototype.parent = Measurement; 31635 VolumeUnit.prototype.constructor = VolumeUnit; 31636 31637 VolumeUnit.ratios = { 31638 /* index, tsp, tbsp, cubic inch us ounce, cup, pint, quart, gallon, cubic foot, milliliter liter, cubic meter, imperial tsp, imperial tbsp, imperial ounce, imperial pint, imperial quart, imperial gal, */ 31639 "tsp" : [1, 1, 0.333333, 0.300781, 0.166667, 0.0208333, 0.0104167, 0.00520833, 0.00130208, 0.000174063, 4.92892, 0.00492892, 4.9289e-6, 0.832674, 0.277558, 0.173474, 0.00867369, 0.00433684, 0.00108421 ], 31640 "tbsp": [2, 3, 1, 0.902344, 0.5, 0.0625, 0.0312, 0.015625, 0.00390625, 0.00052219, 14.7868, 0.0147868, 1.4787e-5, 2.49802, 0.832674, 0.520421, 0.0260211, 0.0130105, 0.00325263 ], 31641 "cubic inch": [3, 3.32468, 1.10823, 1, 0.554113, 0.0692641, 0.034632, 0.017316, 0.004329, 0.000578704, 16.3871, 0.0163871, 1.6387e-5, 2.76837, 0.92279, 0.576744, 0.0288372, 0.0144186, 0.00360465 ], 31642 "us ounce": [4, 6, 2, 1.80469, 1, 0.125, 0.0625, 0.0078125, 0.0078125, 0.00104438, 29.5735, 0.0295735, 2.9574e-5, 4.99604, 1.04084, 1.04084, 0.0520421, 0.0260211, 0.00650526 ], 31643 "cup": [5, 48, 16, 14.4375, 8, 1, 0.5, 0.25, 0.0625, 0.00835503, 236.588, 0.236588, 0.000236588, 39.9683, 13.3228, 8.32674, 0.416337, 0.208168, 0.0520421 ], 31644 "pint": [6, 96, 32, 28.875, 16, 2, 1, 0.5, 0.125, 0.0167101, 473.176, 0.473176, 0.000473176, 79.9367, 26.6456, 16.6535, 0.832674, 0.416337, 0.104084 ], 31645 "quart": [7, 192, 64, 57.75, 32, 4, 2, 1, 0.25, 0.0334201, 946.353, 0.946353, 0.000946353, 159.873, 53.2911, 33.307, 1.66535, 0.832674, 0.208168 ], 31646 "gallon": [8, 768, 256, 231, 128, 16, 8, 4, 1, 0.133681, 3785.41, 3.78541, 0.00378541, 639.494, 213.165, 133.228, 6.66139, 3.3307, 0.832674 ], 31647 "cubic foot": [9, 5745.04, 1915.01, 1728, 957.506, 119.688, 59.8442, 29.9221, 7.48052, 1, 28316.8, 28.3168, 0.0283168, 4783.74, 1594.58, 996.613, 49.8307, 24.9153, 6.22883 ], 31648 "milliliter": [10, 0.202884, 0.067628, 0.0610237, 0.033814, 0.00422675, 0.00211338, 0.00105669, 0.000264172, 3.5315e-5, 1, 0.001, 1e-6, 0.168936, 0.0563121, 0.0351951, 0.00175975, 0.000879877, 0.000219969 ], 31649 "liter": [11, 202.884, 67.628, 61.0237, 33.814, 4.22675, 2.11338, 1.05669, 0.264172, 0.0353147, 1000, 1, 0.001, 56.3121, 56.3121, 35.191, 1.75975, 0.879877, 0.219969 ], 31650 "cubic meter": [12, 202884, 67628, 61023.7, 33814, 4226.75, 2113.38, 1056.69, 264.172, 35.3147, 1e+6, 1000, 1, 168936, 56312.1, 35195.1, 1759.75, 879.877, 219.969 ], 31651 "imperial tsp": [13, 1.20095, 0.200158, 0.361223, 0.600475, 0.0250198, 0.0125099, 0.00625495, 0.00156374, 0.000209041, 5.91939, 0.00591939, 5.9194e-6, 1, 0.333333, 0.208333, 0.0104167, 0.00520833, 0.00130208 ], 31652 "imperial tbsp": [14, 3.60285, 1.20095, 1.08367, 0.600475, 0.0750594, 0.0375297, 0.0187649, 0.00469121, 0.000627124, 17.7582, 0.0177582, 1.7758e-5, 3, 1, 0.625, 0.03125, 0.015625, 0.00390625 ], 31653 "imperial ounce": [15, 5.76456, 1.92152, 1.73387, 0.96076, 0.120095, 0.0600475, 0.0300238, 0.00750594, 0.0010034, 28.4131, 0.0284131, 2.8413e-5, 4.8, 1.6, 1, 0.05, 0.025, 0.00625 ], 31654 "imperial pint": [16, 115.291, 38.4304, 34.6774, 19.2152, 2.4019, 1.20095, 0.600475, 0.150119, 0.020068, 568.261, 0.568261, 0.000568261, 96, 32, 20, 1, 0.5, 0.125 ], 31655 "imperial quart": [17, 230.582, 76.8608, 69.3549, 38.4304, 4.8038, 2.4019, 1.20095, 0.300238, 0.0401359, 1136.52, 1.13652, 0.00113652, 192, 64, 40, 2, 1, 0.25 ], 31656 "imperial gallon": [18, 922.33, 307.443, 277.42, 153.722, 19.2152, 9.6076, 4.8038, 1.20095, 0.160544, 4546.09, 4.54609, 0.00454609, 768, 256, 160, 8, 4, 1 ] 31657 }; 31658 31659 /** 31660 * Return the type of this measurement. Examples are "mass", 31661 * "length", "speed", etc. Measurements can only be converted 31662 * to measurements of the same type.<p> 31663 * 31664 * The type of the units is determined automatically from the 31665 * units. For example, the unit "grams" is type "mass". Use the 31666 * static call {@link Measurement.getAvailableUnits} 31667 * to find out what units this version of ilib supports. 31668 * 31669 * @return {string} the name of the type of this measurement 31670 */ 31671 VolumeUnit.prototype.getMeasure = function() { 31672 return "volume"; 31673 }; 31674 31675 /** 31676 * Return a new measurement instance that is converted to a new 31677 * measurement unit. Measurements can only be converted 31678 * to measurements of the same type.<p> 31679 * 31680 * @param {string} to The name of the units to convert to 31681 * @return {Measurement|undefined} the converted measurement 31682 * or undefined if the requested units are for a different 31683 * measurement type 31684 */ 31685 VolumeUnit.prototype.convert = function(to) { 31686 if (!to || typeof(VolumeUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31687 return undefined; 31688 } 31689 return new VolumeUnit({ 31690 unit: to, 31691 amount: this 31692 }); 31693 }; 31694 31695 VolumeUnit.aliases = { 31696 "US gal": "gallon", 31697 "US gallon": "gallon", 31698 "US Gal": "gallon", 31699 "US Gallons": "gallon", 31700 "Gal(US)": "gallon", 31701 "gal(US)": "gallon", 31702 "gallon": "gallon", 31703 "quart": "quart", 31704 "US quart": "quart", 31705 "US quarts": "quart", 31706 "US Quart": "quart", 31707 "US Quarts": "quart", 31708 "US qt": "quart", 31709 "Qt(US)": "quart", 31710 "qt(US)": "quart", 31711 "US pint": "pint", 31712 "US Pint": "pint", 31713 "pint": "pint", 31714 "pint(US)": "pint", 31715 "Pint(US)": "pint", 31716 "US cup": "cup", 31717 "US Cup": "cup", 31718 "cup(US)": "cup", 31719 "Cup(US)": "cup", 31720 "cup": "cup", 31721 "us ounce": "us ounce", 31722 "US ounce": "us ounce", 31723 "℥": "us ounce", 31724 "US Oz": "us ounce", 31725 "oz(US)": "us ounce", 31726 "Oz(US)": "us ounce", 31727 "US tbsp": "tbsp", 31728 "tbsp": "tbsp", 31729 "tbsp(US)": "tbsp", 31730 "US tablespoon": "tbsp", 31731 "US tsp": "tsp", 31732 "tsp(US)": "tsp", 31733 "tsp": "tsp", 31734 "Cubic meter": "cubic meter", 31735 "cubic meter": "cubic meter", 31736 "Cubic metre": "cubic meter", 31737 "cubic metre": "cubic meter", 31738 "m3": "cubic meter", 31739 "Liter": "liter", 31740 "Liters": "liter", 31741 "liter": "liter", 31742 "L": "liter", 31743 "l": "liter", 31744 "Milliliter": "milliliter", 31745 "ML": "milliliter", 31746 "ml": "milliliter", 31747 "milliliter": "milliliter", 31748 "mL": "milliliter", 31749 "Imperial gal": "imperial gallon", 31750 "imperial gallon": "imperial gallon", 31751 "Imperial gallon": "imperial gallon", 31752 "gallon(imperial)": "imperial gallon", 31753 "gal(imperial)": "imperial gallon", 31754 "Imperial quart": "imperial quart", 31755 "imperial quart": "imperial quart", 31756 "Imperial Quart": "imperial quart", 31757 "IMperial qt": "imperial quart", 31758 "qt(Imperial)": "imperial quart", 31759 "quart(imperial)": "imperial quart", 31760 "Imperial pint": "imperial pint", 31761 "imperial pint": "imperial pint", 31762 "pint(Imperial)": "imperial pint", 31763 "imperial oz": "imperial ounce", 31764 "imperial ounce": "imperial ounce", 31765 "Imperial Ounce": "imperial ounce", 31766 "Imperial tbsp": "imperial tbsp", 31767 "imperial tbsp": "imperial tbsp", 31768 "tbsp(Imperial)": "imperial tbsp", 31769 "Imperial tsp": "imperial tsp", 31770 "imperial tsp": "imperial tsp", 31771 "tsp(Imperial)": "imperial tsp", 31772 "Cubic foot": "cubic foot", 31773 "cubic foot": "cubic foot", 31774 "Cubic Foot": "cubic foot", 31775 "Cubic feet": "cubic foot", 31776 "cubic Feet": "cubic foot", 31777 "cubic ft": "cubic foot", 31778 "ft3": "cubic foot", 31779 "Cubic inch": "cubic inch", 31780 "Cubic inches": "cubic inch", 31781 "cubic inches": "cubic inch", 31782 "cubic inch": "cubic inch", 31783 "cubic in": "cubic inch", 31784 "cu in": "cubic inch", 31785 "cu inch": "cubic inch", 31786 "inch³": "cubic inch", 31787 "in³": "cubic inch", 31788 "inch^3": "cubic inch", 31789 "in^3": "cubic inch", 31790 "c.i": "cubic inch", 31791 "CI": "cubic inch", 31792 "cui": "cubic inch" 31793 }; 31794 31795 /** 31796 * Convert a volume to another measure. 31797 * @static 31798 * @param to {string} unit to convert to 31799 * @param from {string} unit to convert from 31800 * @param volume {number} amount to be convert 31801 * @returns {number|undefined} the converted amount 31802 */ 31803 VolumeUnit.convert = function(to, from, volume) { 31804 from = VolumeUnit.aliases[from] || from; 31805 to = VolumeUnit.aliases[to] || to; 31806 var fromRow = VolumeUnit.ratios[from]; 31807 var toRow = VolumeUnit.ratios[to]; 31808 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31809 return undefined; 31810 } 31811 var result = volume * fromRow[toRow[0]]; 31812 return result; 31813 }; 31814 31815 /** 31816 * @private 31817 * @static 31818 */ 31819 VolumeUnit.getMeasures = function () { 31820 var ret = []; 31821 for (var m in VolumeUnit.ratios) { 31822 ret.push(m); 31823 } 31824 return ret; 31825 }; 31826 VolumeUnit.metricSystem = { 31827 "milliliter": 10, 31828 "liter": 11, 31829 "cubic meter": 12 31830 }; 31831 VolumeUnit.imperialSystem = { 31832 "imperial tsp": 13, 31833 "imperial tbsp": 14, 31834 "imperial ounce": 15, 31835 "imperial pint": 16, 31836 "imperial quart": 17, 31837 "imperial gallon": 18 31838 }; 31839 VolumeUnit.uscustomarySystem = { 31840 "tsp": 1, 31841 "tbsp": 2, 31842 "cubic inch": 3, 31843 "us ounce": 4, 31844 "cup": 5, 31845 "pint": 6, 31846 "quart": 7, 31847 "gallon": 8, 31848 "cubic foot": 9 31849 }; 31850 31851 VolumeUnit.metricToUScustomary = { 31852 "milliliter": "tsp", 31853 "liter": "quart", 31854 "cubic meter": "cubic foot" 31855 }; 31856 VolumeUnit.metricToImperial = { 31857 "milliliter": "imperial tsp", 31858 "liter": "imperial quart", 31859 "cubic meter": "imperial gallon" 31860 }; 31861 31862 VolumeUnit.imperialToMetric = { 31863 "imperial tsp": "milliliter", 31864 "imperial tbsp": "milliliter", 31865 "imperial ounce": "milliliter", 31866 "imperial pint": "liter", 31867 "imperial quart": "liter", 31868 "imperial gallon": "cubic meter" 31869 }; 31870 VolumeUnit.imperialToUScustomary = { 31871 "imperial tsp": "tsp", 31872 "imperial tbsp": "tbsp", 31873 "imperial ounce": "us ounce", 31874 "imperial pint": "pint", 31875 "imperial quart": "quart", 31876 "imperial gallon": "gallon" 31877 }; 31878 31879 VolumeUnit.uScustomaryToImperial = { 31880 "tsp": "imperial tsp", 31881 "tbsp": "imperial tbsp", 31882 "cubic inch": "imperial tbsp", 31883 "us ounce": "imperial ounce", 31884 "cup": "imperial ounce", 31885 "pint": "imperial pint", 31886 "quart": "imperial quart", 31887 "gallon": "imperial gallon", 31888 "cubic foot": "imperial gallon" 31889 }; 31890 VolumeUnit.uScustomarylToMetric = { 31891 "tsp": "milliliter", 31892 "tbsp": "milliliter", 31893 "cubic inch": "milliliter", 31894 "us ounce": "milliliter", 31895 "cup": "milliliter", 31896 "pint": "liter", 31897 "quart": "liter", 31898 "gallon": "cubic meter", 31899 "cubic foot": "cubic meter" 31900 }; 31901 31902 /** 31903 * Localize the measurement to the commonly used measurement in that locale. For example 31904 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31905 * the formatted number should be automatically converted to the most appropriate 31906 * measure in the other system, in this case, mph. The formatted result should 31907 * appear as "37.3 mph". 31908 * 31909 * @param {string} locale current locale string 31910 * @returns {Measurement} a new instance that is converted to locale 31911 */ 31912 VolumeUnit.prototype.localize = function(locale) { 31913 var to; 31914 if (locale === "en-US") { 31915 to = VolumeUnit.metricToUScustomary[this.unit] || 31916 VolumeUnit.imperialToUScustomary[this.unit] || 31917 this.unit; 31918 } else if (locale === "en-GB") { 31919 to = VolumeUnit.metricToImperial[this.unit] || 31920 VolumeUnit.uScustomaryToImperial[this.unit] || 31921 this.unit; 31922 } else { 31923 to = VolumeUnit.uScustomarylToMetric[this.unit] || 31924 VolumeUnit.imperialToUScustomary[this.unit] || 31925 this.unit; 31926 } 31927 return new VolumeUnit({ 31928 unit: to, 31929 amount: this 31930 }); 31931 }; 31932 31933 /** 31934 * Scale the measurement unit to an acceptable level. The scaling 31935 * happens so that the integer part of the amount is as small as 31936 * possible without being below zero. This will result in the 31937 * largest units that can represent this measurement without 31938 * fractions. Measurements can only be scaled to other measurements 31939 * of the same type. 31940 * 31941 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31942 * or undefined if the system can be inferred from the current measure 31943 * @return {Measurement} a new instance that is scaled to the 31944 * right level 31945 */ 31946 VolumeUnit.prototype.scale = function(measurementsystem) { 31947 var fromRow = VolumeUnit.ratios[this.unit]; 31948 var mSystem; 31949 31950 if (measurementsystem === "metric"|| (typeof(measurementsystem) === 'undefined' 31951 && typeof(VolumeUnit.metricSystem[this.unit]) !== 'undefined')) { 31952 mSystem = VolumeUnit.metricSystem; 31953 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 31954 && typeof(VolumeUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 31955 mSystem = VolumeUnit.uscustomarySystem; 31956 } else if (measurementsystem === "imperial"|| (typeof(measurementsystem) === 'undefined' 31957 && typeof(VolumeUnit.imperialSystem[this.unit]) !== 'undefined')) { 31958 mSystem = VolumeUnit.imperialSystem; 31959 } 31960 31961 var volume = this.amount; 31962 var munit = this.unit; 31963 31964 volume = 18446744073709551999; 31965 31966 for (var m in mSystem) { 31967 var tmp = this.amount * fromRow[mSystem[m]]; 31968 if (tmp >= 1 && tmp < volume) { 31969 volume = tmp; 31970 munit = m; 31971 } 31972 } 31973 31974 return new VolumeUnit({ 31975 unit: munit, 31976 amount: volume 31977 }); 31978 }; 31979 31980 //register with the factory method 31981 Measurement._constructors["volume"] = VolumeUnit; 31982 31983 31984 31985 /*< MeasurementFactory.js */ 31986 /* 31987 * MeasurementFactory.js - Function to instantiate the appropriate subclasses of 31988 * the Measurement class. 31989 * 31990 * Copyright © 2015, JEDLSoft 31991 * 31992 * Licensed under the Apache License, Version 2.0 (the "License"); 31993 * you may not use this file except in compliance with the License. 31994 * You may obtain a copy of the License at 31995 * 31996 * http://www.apache.org/licenses/LICENSE-2.0 31997 * 31998 * Unless required by applicable law or agreed to in writing, software 31999 * distributed under the License is distributed on an "AS IS" BASIS, 32000 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32001 * 32002 * See the License for the specific language governing permissions and 32003 * limitations under the License. 32004 */ 32005 32006 /* 32007 !depends 32008 UnknownUnit.js 32009 AreaUnit.js 32010 DigitalStorageUnit.js 32011 EnergyUnit.js 32012 FuelConsumptionUnit.js 32013 LengthUnit.js 32014 MassUnit.js 32015 TemperatureUnit.js 32016 TimeUnit.js 32017 VelocityUnit.js 32018 VolumeUnit.js 32019 Measurement.js 32020 */ 32021 32022 // TODO: make these dependencies dynamic or at least generate them in the build 32023 // These will each add themselves to Measurement._constructors[] 32024 32025 32026 /** 32027 * Create a measurement subclass instance based on a particular measure 32028 * required. The measurement is immutable once 32029 * it is created, but it can be converted to other measurements later.<p> 32030 * 32031 * The options may contain any of the following properties: 32032 * 32033 * <ul> 32034 * <li><i>amount</i> - either a numeric amount for this measurement given 32035 * as a number of the specified units, or another Measurement instance 32036 * to convert to the requested units. If converting to new units, the type 32037 * of measure between the other instance's units and the current units 32038 * must be the same. That is, you can only convert one unit of mass to 32039 * another. You cannot convert a unit of mass into a unit of length. 32040 * 32041 * <li><i>unit</i> - units of this measurement. Use the 32042 * static call {@link MeasurementFactory.getAvailableUnits} 32043 * to find out what units this version of ilib supports. If the given unit 32044 * is not a base unit, the amount will be normalized to the number of base units 32045 * and stored as that number of base units. 32046 * For example, if an instance is constructed with 1 kg, this will be converted 32047 * automatically into 1000 g, as grams are the base unit and kg is merely a 32048 * commonly used scale of grams. 32049 * </ul> 32050 * 32051 * Here are some examples of converting a length into new units. 32052 * The first method is via this factory function by passing the old measurement 32053 * in as the "amount" property.<p> 32054 * 32055 * <pre> 32056 * var measurement1 = MeasurementFactory({ 32057 * amount: 5, 32058 * units: "kilometers" 32059 * }); 32060 * var measurement2 = MeasurementFactory({ 32061 * amount: measurement1, 32062 * units: "miles" 32063 * }); 32064 * </pre> 32065 * 32066 * The value in measurement2 will end up being about 3.125 miles.<p> 32067 * 32068 * The second method uses the convert method.<p> 32069 * 32070 * <pre> 32071 * var measurement1 = MeasurementFactory({ 32072 * amount: 5, 32073 * units: "kilometers" 32074 * }); 32075 * var measurement2 = measurement1.convert("miles"); 32076 * }); 32077 * </pre> 32078 * 32079 * The value in measurement2 will again end up being about 3.125 miles. 32080 * 32081 * @static 32082 * @param {Object=} options options that control the construction of this instance 32083 */ 32084 var MeasurementFactory = function(options) { 32085 if (!options || typeof(options.unit) === 'undefined') { 32086 return undefined; 32087 } 32088 32089 var measure = undefined; 32090 32091 for (var c in Measurement._constructors) { 32092 var measurement = Measurement._constructors[c]; 32093 if (typeof(measurement.aliases[options.unit]) !== 'undefined') { 32094 measure = c; 32095 break; 32096 } 32097 } 32098 32099 if (!measure || typeof(measure) === 'undefined') { 32100 return new UnknownUnit({ 32101 unit: options.unit, 32102 amount: options.amount 32103 }); 32104 } else { 32105 return new Measurement._constructors[measure](options); 32106 } 32107 }; 32108 32109 /** 32110 * Return a list of all possible units that this version of ilib supports. 32111 * Typically, the units are given as their full names in English. Unit names 32112 * are case-insensitive. 32113 * 32114 * @static 32115 * @return {Array.<string>} an array of strings containing names of measurement 32116 * units available 32117 */ 32118 MeasurementFactory.getAvailableUnits = function () { 32119 var units = []; 32120 for (var c in Measurement._constructors) { 32121 var measure = Measurement._constructors[c]; 32122 units = units.concat(measure.getMeasures()); 32123 } 32124 return units; 32125 }; 32126 32127 32128 32129 /*< UnitFmt.js */ 32130 /* 32131 * UnitFmt.js - Unit formatter class 32132 * 32133 * Copyright © 2014-2015, JEDLSoft 32134 * 32135 * Licensed under the Apache License, Version 2.0 (the "License"); 32136 * you may not use this file except in compliance with the License. 32137 * You may obtain a copy of the License at 32138 * 32139 * http://www.apache.org/licenses/LICENSE-2.0 32140 * 32141 * Unless required by applicable law or agreed to in writing, software 32142 * distributed under the License is distributed on an "AS IS" BASIS, 32143 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32144 * 32145 * See the License for the specific language governing permissions and 32146 * limitations under the License. 32147 */ 32148 32149 /* 32150 !depends 32151 ilib.js 32152 Locale.js 32153 ResBundle.js 32154 LocaleInfo.js 32155 IString.js 32156 NumFmt.js 32157 Utils.js 32158 */ 32159 32160 // !data unitfmt 32161 32162 32163 32164 /** 32165 * @class 32166 * Create a new unit formatter instance. The unit formatter is immutable once 32167 * it is created, but can format as many different strings with different values 32168 * as needed with the same options. Create different unit formatter instances 32169 * for different purposes and then keep them cached for use later if you have 32170 * more than one unit string to format.<p> 32171 * 32172 * The options may contain any of the following properties: 32173 * 32174 * <ul> 32175 * <li><i>locale</i> - locale to use when formatting the units. The locale also 32176 * controls the translation of the names of the units. If the locale is 32177 * not specified, then the default locale of the app or web page will be used. 32178 * 32179 * <li><i>autoScale</i> - when true, automatically scale the amount to get the smallest 32180 * number greater than 1, where possible, possibly by converting units within the locale's 32181 * measurement system. For example, if the current locale is "en-US", and we have 32182 * a measurement containing 278 fluid ounces, then the number "278" can be scaled down 32183 * by converting the units to a larger one such as gallons. The scaled size would be 32184 * 2.17188 gallons. Since iLib does not have a US customary measure larger than gallons, 32185 * it cannot scale it down any further. If the amount is less than the smallest measure 32186 * already, it cannot be scaled down any further and no autoscaling will be applied. 32187 * Default for the autoScale property is "true", so it only needs to be specified when 32188 * you want to turn off autoscaling. 32189 * 32190 * <li><i>autoConvert</i> - automatically convert the units to the nearest appropriate 32191 * measure of the same type in the measurement system used by the locale. For example, 32192 * if a measurement of length is given in meters, but the current locale is "en-US" 32193 * which uses the US Customary system, then the nearest appropriate measure would be 32194 * "yards", and the amount would be converted from meters to yards automatically before 32195 * being formatted. Default for the autoConvert property is "true", so it only needs to 32196 * be specified when you want to turn off autoconversion. 32197 * 32198 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 32199 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 32200 * the integral part of the number. 32201 * 32202 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 32203 * appear in the formatted output. If the number does not have enough fractional digits 32204 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 32205 * 32206 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 32207 * this property governs how the least significant digits are rounded to conform to that 32208 * maximum. The value of this property is a string with one of the following values: 32209 * <ul> 32210 * <li><i>up</i> - round away from zero 32211 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 32212 * <li><i>ceiling</i> - round towards positive infinity 32213 * <li><i>floor</i> - round towards negative infinity 32214 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 32215 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 32216 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 32217 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 32218 * </ul> 32219 * 32220 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 32221 * loaded. When the onLoad option is given, the UnitFmt object will attempt to 32222 * load any missing locale data using the ilib loader callback. 32223 * When the constructor is done (even if the data is already preassembled), the 32224 * onLoad function is called with the current instance as a parameter, so this 32225 * callback can be used with preassembled or dynamic loading or a mix of the two. 32226 * 32227 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 32228 * asynchronously. If this option is given as "false", then the "onLoad" 32229 * callback must be given, as the instance returned from this constructor will 32230 * not be usable for a while. 32231 * 32232 * <li><i>loadParams</i> - an object containing parameters to pass to the 32233 * loader callback function when locale data is missing. The parameters are not 32234 * interpretted or modified in any way. They are simply passed along. The object 32235 * may contain any property/value pairs as long as the calling code is in 32236 * agreement with the loader callback function as to what those parameters mean. 32237 * </ul> 32238 * 32239 * Here is an example of how you might use the unit formatter to format a string with 32240 * the correct units.<p> 32241 * 32242 * 32243 * @constructor 32244 * @param {Object} options options governing the way this date formatter instance works 32245 */ 32246 var UnitFmt = function(options) { 32247 var sync = true, 32248 loadParams = undefined; 32249 32250 this.length = "long"; 32251 this.scale = true; 32252 this.measurementType = 'undefined'; 32253 this.convert = true; 32254 this.locale = new Locale(); 32255 32256 if (options) { 32257 if (options.locale) { 32258 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 32259 } 32260 32261 if (typeof(options.sync) === 'boolean') { 32262 sync = options.sync; 32263 } 32264 32265 if (typeof(options.loadParams) !== 'undefined') { 32266 loadParams = options.loadParams; 32267 } 32268 32269 if (options.length) { 32270 this.length = options.length; 32271 } 32272 32273 if (typeof(options.autoScale) === 'boolean') { 32274 this.scale = options.autoScale; 32275 } 32276 32277 if (typeof(options.autoConvert) === 'boolean') { 32278 this.convert = options.autoConvert; 32279 } 32280 32281 if (typeof(options.useNative) === 'boolean') { 32282 this.useNative = options.useNative; 32283 } 32284 32285 if (options.measurementSystem) { 32286 this.measurementSystem = options.measurementSystem; 32287 } 32288 32289 if (typeof (options.maxFractionDigits) === 'number') { 32290 /** 32291 * @private 32292 * @type {number|undefined} 32293 */ 32294 this.maxFractionDigits = options.maxFractionDigits; 32295 } 32296 if (typeof (options.minFractionDigits) === 'number') { 32297 /** 32298 * @private 32299 * @type {number|undefined} 32300 */ 32301 this.minFractionDigits = options.minFractionDigits; 32302 } 32303 /** 32304 * @private 32305 * @type {string} 32306 */ 32307 this.roundingMode = options.roundingMode; 32308 } 32309 32310 if (!UnitFmt.cache) { 32311 UnitFmt.cache = {}; 32312 } 32313 32314 Utils.loadData({ 32315 object: UnitFmt, 32316 locale: this.locale, 32317 name: "unitfmt.json", 32318 sync: sync, 32319 loadParams: loadParams, 32320 callback: ilib.bind(this, function (format) { 32321 var formatted = format; 32322 this.template = formatted["unitfmt"][this.length]; 32323 32324 new NumFmt({ 32325 locale: this.locale, 32326 useNative: this.useNative, 32327 maxFractionDigits: this.maxFractionDigits, 32328 minFractionDigits: this.minFractionDigits, 32329 roundingMode: this.roundingMode, 32330 sync: sync, 32331 loadParams: loadParams, 32332 onLoad: ilib.bind(this, function (numfmt) { 32333 this.numFmt = numfmt; 32334 32335 if (options && typeof(options.onLoad) === 'function') { 32336 options.onLoad(this); 32337 } 32338 }) 32339 }); 32340 }) 32341 }); 32342 }; 32343 32344 UnitFmt.prototype = { 32345 32346 /** 32347 * Return the locale used with this formatter instance. 32348 * @return {Locale} the Locale instance for this formatter 32349 */ 32350 getLocale: function() { 32351 return this.locale; 32352 }, 32353 32354 /** 32355 * Return the template string that is used to format date/times for this 32356 * formatter instance. This will work, even when the template property is not explicitly 32357 * given in the options to the constructor. Without the template option, the constructor 32358 * will build the appropriate template according to the options and use that template 32359 * in the format method. 32360 * 32361 * @return {string} the format template for this formatter 32362 */ 32363 getTemplate: function() { 32364 return this.template; 32365 }, 32366 32367 /** 32368 * Convert this formatter to a string representation by returning the 32369 * format template. This method delegates to getTemplate. 32370 * 32371 * @return {string} the format template 32372 */ 32373 toString: function() { 32374 return this.getTemplate(); 32375 }, 32376 32377 /** 32378 * Return whether or not this formatter will auto-scale the units while formatting. 32379 * @returns {boolean} true if auto-scaling is turned on 32380 */ 32381 getScale: function() { 32382 return this.scale; 32383 }, 32384 32385 /** 32386 * Return the measurement system that is used for this formatter. 32387 * @returns {string} the measurement system used in this formatter 32388 */ 32389 getMeasurementSystem: function() { 32390 return this.measurementSystem; 32391 }, 32392 32393 /** 32394 * Format a particular unit instance according to the settings of this 32395 * formatter object. 32396 * 32397 * @param {Measurement} measurement measurement to format 32398 * @return {string} the formatted version of the given date instance 32399 */ 32400 format: function (measurement) { 32401 var u = this.convert ? measurement.localize(this.locale.getSpec()) : measurement; 32402 u = this.scale ? u.scale(this.measurementSystem) : u; 32403 var formatted = new IString(this.template[u.getUnit()]); 32404 // make sure to use the right plural rules 32405 formatted.setLocale(this.locale, true, undefined, undefined); 32406 formatted = formatted.formatChoice(u.amount,{n:this.numFmt.format(u.amount)}); 32407 return formatted.length > 0 ? formatted : u.amount +" " + u.unit; 32408 } 32409 }; 32410 32411 32412 /*< Charset.js */ 32413 /* 32414 * Charset.js - Return information about a particular character set 32415 * 32416 * Copyright © 2014-2015, JEDLSoft 32417 * 32418 * Licensed under the Apache License, Version 2.0 (the "License"); 32419 * you may not use this file except in compliance with the License. 32420 * You may obtain a copy of the License at 32421 * 32422 * http://www.apache.org/licenses/LICENSE-2.0 32423 * 32424 * Unless required by applicable law or agreed to in writing, software 32425 * distributed under the License is distributed on an "AS IS" BASIS, 32426 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32427 * 32428 * See the License for the specific language governing permissions and 32429 * limitations under the License. 32430 */ 32431 32432 // !depends ilib.js Utils.js 32433 // !data charsetaliases charset/ISO-8859-1 charset/ISO-8859-15 charset/UTF-8 32434 32435 32436 /** 32437 * @class 32438 * Create a new character set info instance. Charset instances give information about 32439 * a particular character set, such as whether or not it is single byte or multibyte, 32440 * and which languages commonly use that charset.<p> 32441 * 32442 * The optional options object holds extra parameters if they are necessary. The 32443 * current list of supported options are: 32444 * 32445 * <ul> 32446 * <li><i>name</i> - the name of the charset. This can be given as any commonly 32447 * used name for the character set, which is normalized to a standard IANA name 32448 * before its info is loaded. If a name is not given, 32449 * this class will return information about the base character set of Javascript, 32450 * which is currently Unicode as encoded in UTF-16. 32451 * 32452 * <li><i>onLoad</i> - a callback function to call when this object is fully 32453 * loaded. When the onLoad option is given, this class will attempt to 32454 * load any missing data using the ilib loader callback. 32455 * When the constructor is done (even if the data is already preassembled), the 32456 * onLoad function is called with the current instance as a parameter, so this 32457 * callback can be used with preassembled or dynamic loading or a mix of the two. 32458 * 32459 * <li><i>sync</i> - tell whether to load any missing data synchronously or 32460 * asynchronously. If this option is given as "false", then the "onLoad" 32461 * callback must be given, because the instance returned from this constructor will 32462 * not be usable for a while. 32463 * 32464 * <li><i>loadParams</i> - an object containing parameters to pass to the 32465 * loader callback function when data is missing. The parameters are not 32466 * interpretted or modified in any way. They are simply passed along. The object 32467 * may contain any property/value pairs as long as the calling code is in 32468 * agreement with the loader callback function as to what those parameters mean. 32469 * </ul> 32470 * 32471 * If this copy of ilib is pre-assembled and all the data is already available, 32472 * or if the data was already previously loaded, then this constructor will call 32473 * the onLoad callback immediately when the initialization is done. 32474 * If the onLoad option is not given, this class will only attempt to load any 32475 * missing data synchronously. 32476 * 32477 * Depends directive: !depends charset.js 32478 * 32479 * @constructor 32480 * @see {ilib.setLoaderCallback} for information about registering a loader callback instance 32481 * @param {Object=} options options which govern the construction of this instance 32482 */ 32483 var Charset = function(options) { 32484 var sync = true, 32485 loadParams = undefined; 32486 this.originalName = "UTF-8"; 32487 32488 if (options) { 32489 if (typeof(options.name) !== 'undefined') { 32490 this.originalName = options.name; 32491 } 32492 32493 if (typeof(options.sync) !== 'undefined') { 32494 sync = (options.sync == true); 32495 } 32496 32497 if (typeof(options.loadParams) !== 'undefined') { 32498 loadParams = options.loadParams; 32499 } 32500 } 32501 32502 if (!Charset.cache) { 32503 Charset.cache = {}; 32504 } 32505 32506 // default data. A majority of charsets use this info 32507 this.info = { 32508 description: "default", 32509 min: 1, 32510 max: 1, 32511 bigendian: true, 32512 scripts: ["Latn"], 32513 locales: ["*"] 32514 }; 32515 32516 Utils.loadData({ 32517 object: Charset, 32518 locale: "-", 32519 nonlocale: true, 32520 name: "charsetaliases.json", 32521 sync: sync, 32522 loadParams: loadParams, 32523 callback: ilib.bind(this, function (info) { 32524 // first map the given original name to one of the standardized IANA names 32525 if (info) { 32526 // recognize better by getting rid of extraneous crap and upper-casing 32527 // it so that the match is case-insensitive 32528 var n = this.originalName.replace(/[-_,:\+\.\(\)]/g, '').toUpperCase(); 32529 this.name = info[n]; 32530 } 32531 if (!this.name) { 32532 this.name = this.originalName; 32533 } 32534 Utils.loadData({ 32535 object: Charset, 32536 locale: "-", 32537 nonlocale: true, 32538 name: "charset/" + this.name + ".json", 32539 sync: sync, 32540 loadParams: loadParams, 32541 callback: ilib.bind(this, function (info) { 32542 if (info) { 32543 ilib.extend(this.info, info); 32544 } 32545 if (options && typeof(options.onLoad) === 'function') { 32546 options.onLoad(this); 32547 } 32548 }) 32549 }); 32550 }) 32551 }); 32552 }; 32553 32554 Charset.prototype = { 32555 /** 32556 * Return the standard normalized name of this charset. The list of standard names 32557 * comes from the IANA registry of character set names at 32558 * <a href="http://www.iana.org/assignments/character-sets/character-sets.xhtml">http://www.iana.org/assignments/character-sets/character-sets.xhtml</a>. 32559 * 32560 * @returns {string} the name of the charset 32561 */ 32562 getName: function () { 32563 return this.name; 32564 }, 32565 32566 /** 32567 * Return the original name that this instance was constructed with before it was 32568 * normalized to the standard name returned by {@link #getName}. 32569 * 32570 * @returns {String} the original name that this instance was constructed with 32571 */ 32572 getOriginalName: function() { 32573 return this.originalName; 32574 }, 32575 32576 /** 32577 * Return a short description of the character set. 32578 * 32579 * @returns {string} a description of the character set 32580 */ 32581 getDescription: function() { 32582 return this.info.description || this.getName(); 32583 }, 32584 32585 /** 32586 * Return the smallest number of bytes that a single character in this charset 32587 * could use. For most charsets, this is 1, but for some charsets such as Unicode 32588 * encoded in UTF-16, this may be 2 or more. 32589 * @returns {number} the smallest number of bytes that a single character in 32590 * this charset uses 32591 */ 32592 getMinCharWidth: function () { 32593 return this.info.min; 32594 }, 32595 32596 /** 32597 * Return the largest number of bytes that a single character in this charset 32598 * could use. 32599 * @returns {number} the largest number of bytes that a single character in 32600 * this charset uses 32601 */ 32602 getMaxCharWidth: function () { 32603 return this.info.max; 32604 }, 32605 32606 /** 32607 * Return true if this is a multibyte character set, or false for a fixed 32608 * width character set. A multibyte character set is one in which the characters 32609 * have a variable width. That is, one character may use 1 byte and a different 32610 * character might use 2 or 3 bytes. 32611 * 32612 * @returns {boolean} true if this is a multibyte charset, or false otherwise 32613 */ 32614 isMultibyte: function() { 32615 return this.getMaxCharWidth() > this.getMinCharWidth(); 32616 }, 32617 32618 /** 32619 * Return whether or not characters larger than 1 byte use the big endian order 32620 * or little endian. 32621 * 32622 * @returns {boolean} true if this character set uses big endian order, or false 32623 * otherwise 32624 */ 32625 isBigEndian: function() { 32626 return this.info.bigendian; 32627 }, 32628 32629 /** 32630 * Return an array of ISO script codes whose characters can be encoded with this 32631 * character set. 32632 * 32633 * @returns {Array.<string>} an array of ISO script codes supported by this charset 32634 */ 32635 getScripts: function() { 32636 return this.info.scripts; 32637 } 32638 }; 32639 32640 32641 /*< Charmap.js */ 32642 /* 32643 * Charmap.js - A character set mapping class 32644 * 32645 * Copyright © 2014-2015, JEDLSoft 32646 * 32647 * Licensed under the Apache License, Version 2.0 (the "License"); 32648 * you may not use this file except in compliance with the License. 32649 * You may obtain a copy of the License at 32650 * 32651 * http://www.apache.org/licenses/LICENSE-2.0 32652 * 32653 * Unless required by applicable law or agreed to in writing, software 32654 * distributed under the License is distributed on an "AS IS" BASIS, 32655 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32656 * 32657 * See the License for the specific language governing permissions and 32658 * limitations under the License. 32659 */ 32660 32661 // !depends ilib.js Utils.js Charset.js JSUtils.js IString.js 32662 32663 // !data charset/US-ASCII charset/ISO-10646-UCS-2 charset/ISO-10646-UCS-4 charset/ISO-10646-Unicode-Latin1 32664 32665 32666 /** 32667 * @class 32668 * Create a new default character set mapping instance. This class is the parent 32669 * class of all of the charmapping subclasses, and only implements basic US-ASCII 32670 * mapping. The subclasses implement all other charsets, some algorithmically, and 32671 * some in a table-based way. Use {@link CharmapFactory} to create the correct 32672 * subclass instance for the desired charmap.<p> 32673 * 32674 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 32675 * character set and encoding used by Javascript itself. In order to convert 32676 * between two non-Unicode character sets, you must chain two charmap instances together 32677 * to first map to Unicode and then back to the second charset. <p> 32678 * 32679 * The options parameter controls which mapping is constructed and its behaviours. The 32680 * current list of supported options are: 32681 * 32682 * <ul> 32683 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 32684 * character. For example, if you are mapping Unicode characters to a particular native 32685 * character set that does not support particular Unicode characters, the mapper will 32686 * follow the behaviour specified in this property. Valid values are: 32687 * <ul> 32688 * <li><i>skip</i> - skip any characters that do not exist in the target charset 32689 * <li><i>placeholder</i> - put a static placeholder character in the output string 32690 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 32691 * parameter to specify which character to use in this case 32692 * <li><i>escape</i> - use an escape sequence to represent the unknown character 32693 * </ul> 32694 * The default value for the missing property if not otherwise specified is "escape" 32695 * so that information is not lost. 32696 * 32697 * <li><i>placeholder</i> - specify the placeholder character to use when the 32698 * mapper cannot map a particular input character to the output string. If this 32699 * option is not specified, then the '?' (question mark) character is used where 32700 * possible. 32701 * 32702 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 32703 * escape unknown characters in the input when mapping to native, and what 32704 * style of espcae sequences should be parsed when mapping to Unicode. Valid 32705 * values are: 32706 * <ul> 32707 * <li><i>html</i> - Escape the characters as HTML entities. This would use 32708 * the standard HTML 5.0 (or later) entity names where possible, and numeric 32709 * entities in all other cases. Eg. an "e" with an acute accent would be 32710 * "é" 32711 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 32712 * accent would be "\u00E9". This can also be specified as "c#" as 32713 * it uses a similar escape syntax. 32714 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 32715 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 32716 * acute accent would be "\x00E9". This can also be specified as "c++". 32717 * <li><i>java</i> - Use the Java escape style. This is very similar to the 32718 * the Javascript style, but the backslash has to be escaped twice. Eg. an 32719 * "e" with an acute accent would be "\\u00E9". This can also be specified 32720 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 32721 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 32722 * accent would be "\N{U+00E9}" 32723 * </ul> 32724 * The default if this style is not specified is "js" for Javascript. 32725 * </ul> 32726 * 32727 * If this copy of ilib is pre-assembled and all the data is already available, 32728 * or if the data was already previously loaded, then this constructor will call 32729 * the onLoad callback immediately when the initialization is done. 32730 * If the onLoad option is not given, this class will only attempt to load any 32731 * missing data synchronously. 32732 * 32733 * @constructor 32734 * @param {Object=} options options which govern the construction of this instance 32735 */ 32736 var Charmap = function(options) { 32737 var sync = true, 32738 loadParams = undefined; 32739 32740 this.charset = new Charset({name: "US-ASCII"}); 32741 this.missing = "placeholder"; 32742 this.placeholder = "?"; 32743 this.escapeStyle = "js"; 32744 this.expansionFactor = 1; 32745 32746 if (options) { 32747 if (typeof(options.placeholder) !== 'undefined') { 32748 this.placeholder = options.placeholder; 32749 } 32750 32751 var escapes = { 32752 "html": "html", 32753 "js": "js", 32754 "c#": "js", 32755 "c": "c", 32756 "c++": "c", 32757 "java": "java", 32758 "ruby": "java", 32759 "perl": "perl" 32760 }; 32761 32762 if (typeof(options.escapeStyle) !== 'undefined') { 32763 if (typeof(escapes[options.escapeStyle]) !== 'undefined') { 32764 this.escapeStyle = escapes[options.escapeStyle]; 32765 } 32766 } 32767 32768 if (typeof(options.missing) !== 'undefined') { 32769 if (options.missing === "skip" || options.missing === "placeholder" || options.missing === "escape") { 32770 this.missing = options.missing; 32771 } 32772 } 32773 } 32774 32775 this._calcExpansionFactor(); 32776 }; 32777 32778 /** 32779 * A place for the algorithmic conversions to register themselves as 32780 * they are defined. 32781 * 32782 * @static 32783 * @private 32784 */ 32785 Charmap._algorithms = {}; 32786 32787 Charmap.prototype = { 32788 /** 32789 * Return the standard name of this charmap. All charmaps map from 32790 * Unicode to the native charset, so the name returned from this 32791 * function corresponds to the native charset. 32792 * 32793 * @returns {string} the name of the locale's language in English 32794 */ 32795 getName: function () { 32796 return this.charset.getName(); 32797 }, 32798 32799 /** 32800 * @private 32801 */ 32802 writeNative: function (array, start, value) { 32803 // console.log("Charmap.writeNative: start " + start + " adding " + JSON.stringify(value)); 32804 if (ilib.isArray(value)) { 32805 for (var i = 0; i < value.length; i++) { 32806 array[start+i] = value[i]; 32807 } 32808 32809 return value.length; 32810 } else { 32811 array[start] = value; 32812 return 1; 32813 } 32814 }, 32815 32816 /** 32817 * @private 32818 */ 32819 writeNativeString: function (array, start, string) { 32820 // console.log("Charmap.writeNativeString: start " + start + " adding " + JSON.stringify(string)); 32821 for (var i = 0; i < string.length; i++) { 32822 array[start+i] = string.charCodeAt(i); 32823 } 32824 return string.length; 32825 }, 32826 32827 /** 32828 * @private 32829 */ 32830 _calcExpansionFactor: function() { 32831 var factor = 1; 32832 factor = Math.max(factor, this.charset.getMaxCharWidth()); 32833 switch (this.missing) { 32834 case "placeholder": 32835 if (this.placeholder) { 32836 factor = Math.max(factor, this.placeholder.length); 32837 } 32838 break; 32839 case "escape": 32840 switch (this.escapeStyle) { 32841 case "html": 32842 factor = Math.max(factor, 8); // HHHH; 32843 break; 32844 case "c": 32845 factor = Math.max(factor, 6); // \xHHHH 32846 break; 32847 case "perl": 32848 factor = Math.max(factor, 10); // \N{U+HHHH} 32849 break; 32850 32851 default: 32852 factor = Math.max(factor, 6); // \uHHHH 32853 break; 32854 } 32855 break; 32856 default: 32857 break; 32858 } 32859 32860 this.expansionFactor = factor; 32861 }, 32862 32863 /** 32864 * @private 32865 */ 32866 dealWithMissingChar: function(c) { 32867 var seq = ""; 32868 32869 switch (this.missing) { 32870 case "skip": 32871 // do nothing 32872 break; 32873 32874 case "escape": 32875 var num = (typeof(c) === 'string') ? c.charCodeAt(0) : c; 32876 var bigc = JSUtils.pad(num.toString(16), 4).toUpperCase(); 32877 switch (this.escapeStyle) { 32878 case "html": 32879 seq = "" + bigc + ";"; 32880 break; 32881 case "c": 32882 seq = "\\x" + bigc; 32883 break; 32884 case "java": 32885 seq = "\\\\u" + bigc; 32886 break; 32887 case "perl": 32888 seq = "\\N{U+" + bigc + "}"; 32889 break; 32890 32891 default: 32892 case "js": 32893 seq = "\\u" + bigc; 32894 break; 32895 } 32896 break; 32897 32898 default: 32899 case "placeholder": 32900 seq = this.placeholder; 32901 break; 32902 } 32903 32904 return seq; 32905 }, 32906 32907 /** 32908 * Map a string to the native character set. This string may be 32909 * given as an intrinsic Javascript string object or an IString 32910 * object. 32911 * 32912 * @param {string|IString} string string to map to a different 32913 * character set. 32914 * @return {Uint8Array} An array of bytes representing the string 32915 * in the native character set 32916 */ 32917 mapToNative: function(string) { 32918 if (!string) { 32919 return new Uint8Array(0); 32920 } 32921 32922 if (this.algorithm) { 32923 return this.algorithm.mapToNative(string); 32924 } 32925 32926 // the default algorithm is plain old ASCII 32927 var str = (string instanceof IString) ? string : new IString(string); 32928 32929 // use IString's iterator so that we take care of walking through 32930 // the code points correctly, including the surrogate pairs 32931 var c, i = 0, j = 0, it = str.iterator(); 32932 var ret = new Uint8Array(str.length * this.expansionFactor); 32933 32934 while (it.hasNext() && i < ret.length) { 32935 c = it.next(); 32936 if (c < 127) { 32937 ret[i++] = c; 32938 } else { 32939 i += this.writeNativeString(ret, i, this.dealWithMissingChar(c)); 32940 } 32941 } 32942 32943 return ret; 32944 }, 32945 32946 /** 32947 * Map a native string to the standard Javascript charset of UTF-16. 32948 * This string may be given as an array of numbers where each number 32949 * represents a code point in the "from" charset, or as a Uint8Array 32950 * array of bytes representing the bytes of the string in order. 32951 * 32952 * @param {Array.<number>|Uint8Array} bytes bytes to map to 32953 * a Unicode string 32954 * @return {string} A string in the standard Javascript charset UTF-16 32955 */ 32956 mapToUnicode: function(bytes) { 32957 var ret = ""; 32958 var c, i = 0; 32959 32960 while (i < bytes.length) { 32961 c = bytes[i]; 32962 32963 // the default algorithm is plain old ASCII 32964 if (c < 128) { 32965 ret += String.fromCharCode(c); 32966 } else { 32967 // The byte at "i" wasn't ASCII 32968 ret += this.dealWithMissingChar(bytes[i++]); 32969 } 32970 } 32971 32972 return ret; 32973 } 32974 }; 32975 32976 32977 /*< CharmapTable.js */ 32978 /* 32979 * CharmapTable.js - A character set mapping class that maps using trie table 32980 * 32981 * Copyright © 2014-2015, JEDLSoft 32982 * 32983 * Licensed under the Apache License, Version 2.0 (the "License"); 32984 * you may not use this file except in compliance with the License. 32985 * You may obtain a copy of the License at 32986 * 32987 * http://www.apache.org/licenses/LICENSE-2.0 32988 * 32989 * Unless required by applicable law or agreed to in writing, software 32990 * distributed under the License is distributed on an "AS IS" BASIS, 32991 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32992 * 32993 * See the License for the specific language governing permissions and 32994 * limitations under the License. 32995 */ 32996 32997 // !depends ilib.js Utils.js Charset.js Charmap.js IString.js 32998 32999 // !data charmaps/ISO-8859-1 charset/ISO-8859-1 33000 33001 33002 /** 33003 * @class 33004 * Create a new character set mapping instance using based on a trie table. Charmap 33005 * instances map strings to 33006 * other character sets. The charsets can be of any type, single-byte, multi-byte, 33007 * shifting, etc. <p> 33008 * 33009 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 33010 * character set and encoding used by Javascript itself. In order to convert 33011 * between two non-Unicode character sets, you must chain two charmap instances together 33012 * to first map to Unicode and then back to the second charset. <p> 33013 * 33014 * The options parameter controls which mapping is constructed and its behaviours. The 33015 * current list of supported options are: 33016 * 33017 * <ul> 33018 * <li><i>charset</i> - the name of the native charset to map to or from. This can be 33019 * given as an {@link Charset} instance or as a string that contains any commonly used name 33020 * for the character set, which is normalized to a standard IANA name. 33021 * If a name is not given, this class will default to the Western European character 33022 * set called ISO-8859-15. 33023 * 33024 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 33025 * character. For example, if you are mapping Unicode characters to a particular native 33026 * character set that does not support particular Unicode characters, the mapper will 33027 * follow the behaviour specified in this property. Valid values are: 33028 * <ul> 33029 * <li><i>skip</i> - skip any characters that do not exist in the target charset 33030 * <li><i>placeholder</i> - put a static placeholder character in the output string 33031 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 33032 * parameter to specify which character to use in this case 33033 * <li><i>escape</i> - use an escape sequence to represent the unknown character 33034 * </ul> 33035 * The default value for the missing property if not otherwise specified is "escape" 33036 * so that information is not lost. 33037 * 33038 * <li><i>placeholder</i> - specify the placeholder character to use when the 33039 * mapper cannot map a particular input character to the output string. If this 33040 * option is not specified, then the '?' (question mark) character is used where 33041 * possible. 33042 * 33043 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 33044 * escape unknown characters in the input when mapping to native, and what 33045 * style of espcae sequences should be parsed when mapping to Unicode. Valid 33046 * values are: 33047 * <ul> 33048 * <li><i>html</i> - Escape the characters as HTML entities. This would use 33049 * the standard HTML 5.0 (or later) entity names where possible, and numeric 33050 * entities in all other cases. Eg. an "e" with an acute accent would be 33051 * "é" 33052 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 33053 * accent would be "\u00E9". This can also be specified as "c#" as 33054 * it uses a similar escape syntax. 33055 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 33056 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 33057 * acute accent would be "\x00E9". This can also be specified as "c++". 33058 * <li><i>java</i> - Use the Java escape style. This is very similar to the 33059 * the Javascript style, but the backslash has to be escaped twice. Eg. an 33060 * "e" with an acute accent would be "\\u00E9". This can also be specified 33061 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 33062 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 33063 * accent would be "\N{U+00E9}" 33064 * </ul> 33065 * The default if this style is not specified is "js" for Javascript. 33066 * 33067 * <li><i>onLoad</i> - a callback function to call when this object is fully 33068 * loaded. When the onLoad option is given, this class will attempt to 33069 * load any missing data using the ilib loader callback. 33070 * When the constructor is done (even if the data is already preassembled), the 33071 * onLoad function is called with the current instance as a parameter, so this 33072 * callback can be used with preassembled or dynamic loading or a mix of the two. 33073 * 33074 * <li><i>sync</i> - tell whether to load any missing data synchronously or 33075 * asynchronously. If this option is given as "false", then the "onLoad" 33076 * callback must be given, because the instance returned from this constructor will 33077 * not be usable for a while. 33078 * 33079 * <li><i>loadParams</i> - an object containing parameters to pass to the 33080 * loader callback function when data is missing. The parameters are not 33081 * interpretted or modified in any way. They are simply passed along. The object 33082 * may contain any property/value pairs as long as the calling code is in 33083 * agreement with the loader callback function as to what those parameters mean. 33084 * </ul> 33085 * 33086 * If this copy of ilib is pre-assembled and all the data is already available, 33087 * or if the data was already previously loaded, then this constructor will call 33088 * the onLoad callback immediately when the initialization is done. 33089 * If the onLoad option is not given, this class will only attempt to load any 33090 * missing data synchronously. 33091 * 33092 * @constructor 33093 * @see {ilib.setLoaderCallback} for information about registering a loader callback instance 33094 * @extends Charmap 33095 * @param {Object=} options options which govern the construction of this instance 33096 */ 33097 var CharmapTable = function(options) { 33098 var sync = true, 33099 loadParams = undefined; 33100 33101 // console.log("CharmapTable: constructor with options: " + JSON.stringify(options)); 33102 33103 this.parent.call(this, options); 33104 33105 if (options) { 33106 if (typeof(options.charset) === "object") { 33107 this.charset = options.charset; 33108 } else if (typeof(options.name) !== 'undefined') { 33109 this.charset = new Charset({name: options.name}); 33110 } 33111 33112 if (typeof(options.sync) !== 'undefined') { 33113 sync = (options.sync == true); 33114 } 33115 33116 if (typeof(options.loadParams) !== 'undefined') { 33117 loadParams = options.loadParams; 33118 } 33119 } 33120 33121 if (!this.charset) { 33122 this.charset = new Charset({name: "ISO-8859-15"}); 33123 } 33124 33125 this._calcExpansionFactor(); 33126 33127 if (!Charmap.cache) { 33128 Charmap.cache = {}; 33129 } 33130 33131 Utils.loadData({ 33132 object: Charmap, 33133 locale: "-", 33134 nonlocale: true, 33135 name: "charmaps/" + this.charset.getName() + ".json", 33136 sync: sync, 33137 loadParams: loadParams, 33138 callback: ilib.bind(this, function (mapping) { 33139 if (!mapping) { 33140 throw "No mapping found for " + this.charset.getName(); 33141 } 33142 33143 /** @type {{from:Object,to:Object}} */ 33144 this.map = mapping; 33145 if (options && typeof(options.onLoad) === 'function') { 33146 options.onLoad(this); 33147 } 33148 }) 33149 }); 33150 }; 33151 33152 CharmapTable.prototype = new Charmap(); 33153 CharmapTable.prototype.parent = Charmap; 33154 CharmapTable.prototype.constructor = CharmapTable; 33155 33156 /** 33157 * Walk a trie to find the value for the current position in the given array. 33158 * @private 33159 */ 33160 CharmapTable.prototype._trieWalk = function(trie, array, start) { 33161 function isValue(node) { 33162 return (typeof(node) === 'string' || typeof(node) === 'number' || 33163 (typeof(node) === 'object' && ilib.isArray(node))); 33164 } 33165 33166 var lastLeaf = undefined, 33167 i = start, 33168 trienode = trie; 33169 33170 while (i < array.length) { 33171 if (typeof(trienode.__leaf) !== 'undefined') { 33172 lastLeaf = { 33173 consumed: i - start + 1, 33174 value: trienode.__leaf 33175 }; 33176 } 33177 if (array[i] === 0) { 33178 // null-terminator, so end the mapping. 33179 return { 33180 consumed: 1, 33181 value: 0 33182 }; 33183 } else if (typeof(trienode[array[i]]) !== 'undefined') { 33184 // we have a mapping 33185 if (isValue(trienode[array[i]])) { 33186 // it is a leaf node 33187 return { 33188 consumed: i - start + 1, 33189 value: trienode[array[i]] 33190 }; 33191 } else { 33192 // it is an intermediate node 33193 trienode = trienode[array[i++]]; 33194 } 33195 } else { 33196 // no mapping for this array element, so return the last known 33197 // leaf. If none, this will return undefined. 33198 return lastLeaf; 33199 } 33200 } 33201 33202 return undefined; 33203 }; 33204 33205 /** 33206 * Map a string to the native character set. This string may be 33207 * given as an intrinsic Javascript string object or an IString 33208 * object. 33209 * 33210 * @param {string|IString} string string to map to a different 33211 * character set. 33212 * @return {Uint8Array} An array of bytes representing the string 33213 * in the native character set 33214 */ 33215 CharmapTable.prototype.mapToNative = function(string) { 33216 if (!string) { 33217 return new Uint8Array(0); 33218 } 33219 33220 var str = (string instanceof IString) ? string : new IString(string); 33221 33222 // use IString's iterator so that we take care of walking through 33223 // the code points correctly, including the surrogate pairs 33224 // var c, i = 0, it = str.charIterator(); 33225 var ret = new Uint8Array(str.length * this.expansionFactor); 33226 33227 var i = 0, j = 0; 33228 33229 while (i < string.length) { 33230 var result = this._trieWalk(this.map.from, string, i); 33231 if (result) { 33232 if (result.value) { 33233 i += result.consumed; 33234 j += this.writeNative(ret, j, result.value); 33235 } else { 33236 // null-termination 33237 i = string.length; 33238 this.writeNative(ret, j, [result.value]); 33239 } 33240 } else { 33241 // The unicode char at "i" didn't have any mapping, so 33242 // deal with the missing char 33243 j += this.writeNativeString(ret, j, this.dealWithMissingChar(string[i++])); 33244 } 33245 } 33246 33247 return ret.subarray(0, j); 33248 }; 33249 33250 /** 33251 * Map a native string to the standard Javascript charset of UTF-16. 33252 * This string may be given as an array of numbers where each number 33253 * represents a code point in the "from" charset, or as a Uint8Array 33254 * array of bytes representing the bytes of the string in order. 33255 * 33256 * @param {Array.<number>|Uint8Array} bytes bytes to map to 33257 * a Unicode string 33258 * @return {string} A string in the standard Javascript charset UTF-16 33259 */ 33260 CharmapTable.prototype.mapToUnicode = function(bytes) { 33261 var ret = ""; 33262 var i = 0; 33263 33264 while (i < bytes.length) { 33265 var result = this._trieWalk(this.map.to, bytes, i); 33266 if (result) { 33267 if (result.value) { 33268 i += result.consumed; 33269 if (typeof(result.value) === 'string') { 33270 ret += result.value; 33271 } else if (ilib.isArray(result.value)) { 33272 for (var j = 0; j < result.value.length; j++) { 33273 ret += result.value[j]; 33274 } 33275 } // else error in charmap file?? 33276 } else { 33277 // null-termination 33278 i = bytes.length; 33279 } 33280 } else { 33281 // The byte at "i" wasn't a lead byte, so start again at the 33282 // next byte instead. This may synchronize the rest 33283 // of the string. 33284 ret += this.dealWithMissingChar(bytes[i++]); 33285 } 33286 } 33287 33288 return ret; 33289 }; 33290 33291 Charmap._algorithms["CharmapTable"] = CharmapTable; 33292 33293 33294 /*< CharmapFactory.js */ 33295 /* 33296 * CharmapFactory.js - Factory class to create the right subclasses of a charmap for any 33297 * given chararacter set. 33298 * 33299 * Copyright © 2015, JEDLSoft 33300 * 33301 * Licensed under the Apache License, Version 2.0 (the "License"); 33302 * you may not use this file except in compliance with the License. 33303 * You may obtain a copy of the License at 33304 * 33305 * http://www.apache.org/licenses/LICENSE-2.0 33306 * 33307 * Unless required by applicable law or agreed to in writing, software 33308 * distributed under the License is distributed on an "AS IS" BASIS, 33309 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33310 * 33311 * See the License for the specific language governing permissions and 33312 * limitations under the License. 33313 */ 33314 33315 /* !depends ilib.js JSUtils.js Charmap.js CharmapTable.js */ 33316 // !data charset/ISO-8859-15 charmaps/ISO-8859-15 33317 33318 33319 33320 /** 33321 * Factory method to create a new instance of a character set mapping (charmap) 33322 * subclass that is appropriate for the requested charset. Charmap instances map strings to 33323 * other character sets. The charsets can be of any type, single-byte, multi-byte, 33324 * shifting, etc. <p> 33325 * 33326 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 33327 * character set and encoding used by Javascript itself. In order to convert 33328 * between two non-Unicode character sets, you must chain two charmap instances together 33329 * to first map to Unicode and then back to the second charset. <p> 33330 * 33331 * The options parameter controls which mapping is constructed and its behaviours. The 33332 * current list of supported options are: 33333 * 33334 * <ul> 33335 * <li><i>name</i> - the name of the native charset to map to or from. This can be 33336 * given as an {@link Charset} instance or as a string that contains any commonly used name 33337 * for the character set, which is normalized to a standard IANA name. 33338 * If a name is not given, this class will default to the Western European character 33339 * set called ISO-8859-15. 33340 * 33341 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 33342 * character. For example, if you are mapping Unicode characters to a particular native 33343 * character set that does not support particular Unicode characters, the mapper will 33344 * follow the behaviour specified in this property. Valid values are: 33345 * <ul> 33346 * <li><i>skip</i> - skip any characters that do not exist in the target charset 33347 * <li><i>placeholder</i> - put a static placeholder character in the output string 33348 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 33349 * parameter to specify which character to use in this case 33350 * <li><i>escape</i> - use an escape sequence to represent the unknown character 33351 * </ul> 33352 * The default value for the missing property if not otherwise specified is "escape" 33353 * so that information is not lost. 33354 * 33355 * <li><i>placeholder</i> - specify the placeholder character to use when the 33356 * mapper cannot map a particular input character to the output string. If this 33357 * option is not specified, then the '?' (question mark) character is used where 33358 * possible. 33359 * 33360 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 33361 * escape unknown characters in the input when mapping to native, and what 33362 * style of espcae sequences should be parsed when mapping to Unicode. Valid 33363 * values are: 33364 * <ul> 33365 * <li><i>html</i> - Escape the characters as HTML entities. This would use 33366 * the standard HTML 5.0 (or later) entity names where possible, and numeric 33367 * entities in all other cases. Eg. an "e" with an acute accent would be 33368 * "é" 33369 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 33370 * accent would be "\u00E9". This can also be specified as "c#" as 33371 * it uses a similar escape syntax. 33372 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 33373 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 33374 * acute accent would be "\x00E9". This can also be specified as "c++". 33375 * <li><i>java</i> - Use the Java escape style. This is very similar to the 33376 * the Javascript style, but the backslash has to be escaped twice. Eg. an 33377 * "e" with an acute accent would be "\\u00E9". This can also be specified 33378 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 33379 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 33380 * accent would be "\N{U+00E9}" 33381 * </ul> 33382 * The default if this style is not specified is "js" for Javascript. 33383 * 33384 * <li><i>onLoad</i> - a callback function to call when this object is fully 33385 * loaded. When the onLoad option is given, this class will attempt to 33386 * load any missing data using the ilib loader callback. 33387 * When the constructor is done (even if the data is already preassembled), the 33388 * onLoad function is called with the current instance as a parameter, so this 33389 * callback can be used with preassembled or dynamic loading or a mix of the two. 33390 * 33391 * <li><i>sync</i> - tell whether to load any missing data synchronously or 33392 * asynchronously. If this option is given as "false", then the "onLoad" 33393 * callback must be given, because the instance returned from this constructor will 33394 * not be usable for a while. 33395 * 33396 * <li><i>loadParams</i> - an object containing parameters to pass to the 33397 * loader callback function when data is missing. The parameters are not 33398 * interpretted or modified in any way. They are simply passed along. The object 33399 * may contain any property/value pairs as long as the calling code is in 33400 * agreement with the loader callback function as to what those parameters mean. 33401 * </ul> 33402 * 33403 * If this copy of ilib is pre-assembled and all the data is already available, 33404 * or if the data was already previously loaded, then this constructor will call 33405 * the onLoad callback immediately when the initialization is done. 33406 * If the onLoad option is not given, this class will only attempt to load any 33407 * missing data synchronously. 33408 * 33409 * @static 33410 * @param {Object=} options options controlling the construction of this instance, or 33411 * undefined to use the default options 33412 * @return {Charmap|undefined} an instance of a character set mapping class appropriate for 33413 * the requested charset, or undefined if no mapper could be found that supports the 33414 * requested charset 33415 */ 33416 var CharmapFactory = function(options) { 33417 var charsetName = (options && options.name) || "ISO-8859-15"; 33418 var sync = true; 33419 33420 // console.log("CharmapFactory: called with options: " + JSON.stringify(options)); 33421 33422 if (options) { 33423 if (typeof(options.sync) === 'boolean') { 33424 sync = options.sync; 33425 } 33426 } 33427 33428 var instance; 33429 33430 new Charset({ 33431 name: charsetName, 33432 sync: sync, 33433 loadParams: options && options.loadParams, 33434 onLoad: function (charset) { 33435 // name will be normalized already 33436 var cons, name = charset.getName(); 33437 33438 // console.log("CharmapFactory: normalized charset name: " + name); 33439 33440 if (!Charmap._algorithms[name] && ilib.isDynCode()) { 33441 // console.log("CharmapFactory: isDynCode. Doing require"); 33442 var entry = CharmapFactory._dynMap[name] || "CharmapTable"; 33443 cons = Charmap._algorithms[name] = require("./" + entry + ".js"); 33444 } 33445 33446 if (!cons) { 33447 cons = Charmap._algorithms[name] || Charmap._algorithms["CharmapTable"]; 33448 } 33449 33450 // console.log("CharmapFactory: cons is "); console.dir(cons); 33451 33452 // pass the same options through to the constructor so the subclass 33453 // has the ability to do something with if it needs to 33454 instance = cons && new cons(JSUtils.merge(options || {}, {charset: charset})); 33455 } 33456 }); 33457 33458 return instance; 33459 }; 33460 33461 33462 /** 33463 * Map standardized charset names to classes to initialize in the dynamic code model. 33464 * These classes implement algorithmic mappings instead of table-based ones. 33465 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 33466 * @private 33467 */ 33468 CharmapFactory._dynMap = { 33469 "UTF-8": "UTF8", 33470 "UTF-16": "UTF16LE", 33471 "UTF-16LE": "UTF16LE", 33472 "UTF-16BE": "UTF16BE", 33473 "US-ASCII": "Charmap" 33474 /* 33475 not implemented yet 33476 "ISO-2022-JP": "ISO2022", 33477 "ISO-2022-JP-1": "ISO2022", 33478 "ISO-2022-JP-2": "ISO2022", 33479 "ISO-2022-JP-3": "ISO2022", 33480 "ISO-2022-JP-2004": "ISO2022", 33481 "ISO-2022-CN": "ISO2022", 33482 "ISO-2022-CN-EXT": "ISO2022", 33483 "ISO-2022-KR": "ISO2022" 33484 */ 33485 }; 33486 33487 33488 /*< UTF8.js */ 33489 /* 33490 * UTF8.js - Implement Unicode Transformation Format 8-bit mappings 33491 * 33492 * Copyright © 2014-2015, JEDLSoft 33493 * 33494 * Licensed under the Apache License, Version 2.0 (the "License"); 33495 * you may not use this file except in compliance with the License. 33496 * You may obtain a copy of the License at 33497 * 33498 * http://www.apache.org/licenses/LICENSE-2.0 33499 * 33500 * Unless required by applicable law or agreed to in writing, software 33501 * distributed under the License is distributed on an "AS IS" BASIS, 33502 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33503 * 33504 * See the License for the specific language governing permissions and 33505 * limitations under the License. 33506 */ 33507 33508 // !depends Charmap.js IString.js 33509 33510 // !data charset/UTF-8 33511 33512 33513 /** 33514 * @class 33515 * Create a new UTF-8 mapping instance 33516 * @constructor 33517 * @extends Charmap 33518 */ 33519 var UTF8 = function (options) { 33520 this.charset = new Charset({name: "UTF-8"}); 33521 }; 33522 33523 UTF8.prototype = new Charmap(); 33524 UTF8.prototype.parent = Charmap; 33525 UTF8.prototype.constructor = UTF8; 33526 33527 UTF8.prototype.validate = function(bytes) { 33528 var i = 0; 33529 while (i < bytes.length) { 33530 if ((bytes[i] & 0x80) === 0) { 33531 i++; 33532 } else { 33533 var len; 33534 if ((bytes[i] & 0xC0) === 0xC0) { 33535 len = 2; 33536 } else if ((bytes[i] & 0xE0) === 0xE0) { 33537 len = 3; 33538 } else if ((bytes[i] & 0xF0) === 0xF0) { 33539 len = 4; 33540 } else { 33541 // invalid lead byte 33542 return false; 33543 } 33544 if (i + len > bytes.length) { 33545 // not enough trailing bytes 33546 return false; 33547 } 33548 for (var j = 1; j < len; j++) { 33549 // check each trailing byte to see if it has the correct form 33550 if ((bytes[i+j] & 0x80) !== 0x80) { 33551 return false; 33552 } 33553 } 33554 i += len; 33555 } 33556 } 33557 33558 return true; 33559 }; 33560 33561 UTF8.prototype.mapToUnicode = function (bytes) { 33562 if (typeof(Buffer) !== "undefined") { 33563 // nodejs can convert it quickly in native code 33564 var b = new Buffer(bytes); 33565 return b.toString("utf8"); 33566 } 33567 // otherwise we have to implement it in pure JS 33568 var ret = ""; 33569 var i = 0; 33570 while (i < bytes.length) { 33571 if (bytes[i] === 0) { 33572 // null-terminator 33573 i = bytes.length; 33574 } else if ((bytes[i] & 0x80) === 0) { 33575 // 1 byte char 33576 ret += String.fromCharCode(bytes[i++]); 33577 } else if ((bytes[i] & 0xE0) === 0xC0) { 33578 // 2 byte char 33579 if (i + 1 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80) { 33580 throw "invalid utf-8 bytes"; 33581 } 33582 // xxx xxyyyyyy 33583 ret += String.fromCharCode((bytes[i] & 0x1F) << 6 | (bytes[i+1] & 0x3F)); 33584 i += 2; 33585 } else if ((bytes[i] & 0xF0) === 0xE0) { 33586 // 3 byte char 33587 if (i + 2 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80) { 33588 throw "invalid utf-8 bytes"; 33589 } 33590 // xxxxyyyy yyzzzzzz 33591 ret += String.fromCharCode((bytes[i] & 0xF) << 12 | (bytes[i+1] & 0x3F) << 6 | (bytes[i+2] & 0x3F)); 33592 i += 3; 33593 } else if ((bytes[i] & 0xF8) === 0xF0) { 33594 // 4 byte char 33595 if (i + 3 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80 || (bytes[i+3] & 0x80) !== 0x80) { 33596 throw "invalid utf-8 bytes"; 33597 } 33598 // wwwxx xxxxyyyy yyzzzzzz 33599 ret += IString.fromCodePoint((bytes[i] & 0x7) << 18 | (bytes[i+1] & 0x3F) << 12 | (bytes[i+2] & 0x3F) << 6 | (bytes[i+3] & 0x3F)); 33600 i += 4; 33601 } else { 33602 throw "invalid utf-8 bytes"; 33603 } 33604 } 33605 33606 return ret; 33607 }; 33608 33609 UTF8.prototype.mapToNative = function(str) { 33610 if (typeof(Buffer) !== "undefined") { 33611 // nodejs can convert it quickly in native code 33612 var b = new Buffer(str, "utf8"); 33613 return new Uint8Array(b); 33614 } 33615 // otherwise we have to implement it in pure JS 33616 var istr = (str instanceof IString) ? str : new IString(str); 33617 33618 // step through the surrogate pairs as single code points by using 33619 // IString's iterator 33620 var it = istr.iterator(); 33621 33622 // multiply by 4 because the max size of a UTF-8 char is 4 bytes, so 33623 // this will at least get us enough room to encode everything. Add 1 33624 // for the null terminator 33625 var ret = new Uint8Array(istr.length * 4 + 1); 33626 var i = 0; 33627 33628 while (it.hasNext()) { 33629 var c = it.next(); 33630 if (c > 0x7F) { 33631 if (c > 0x7FF) { 33632 if (c > 0xFFFF) { 33633 // astral planes char 33634 ret[i] = 0xF0 | ((c >> 18) & 0x3); 33635 ret[i+1] = 0x80 | ((c >> 12) & 0x3F); 33636 ret[i+2] = 0x80 | ((c >> 6) & 0x3F); 33637 ret[i+3] = 0x80 | (c & 0x3F); 33638 33639 i += 4; 33640 } else { 33641 ret[i] = 0xE0 | ((c >> 12) & 0xF); 33642 ret[i+1] = 0x80 | ((c >> 6) & 0x3F); 33643 ret[i+2] = 0x80 | (c & 0x3F); 33644 33645 i += 3; 33646 } 33647 } else { 33648 ret[i] = 0xC0 | ((c >> 6) & 0x1F); 33649 ret[i+1] = 0x80 | (c & 0x3F); 33650 33651 i += 2; 33652 } 33653 } else { 33654 ret[i++] = (c & 0x7F); 33655 } 33656 } 33657 ret[i] = 0; // null-terminate it 33658 33659 return ret; 33660 }; 33661 33662 Charmap._algorithms["UTF-8"] = UTF8; 33663 33664 33665 /*< UTF16BE.js */ 33666 /* 33667 * UTF16BE.js - Implement Unicode Transformation Format 16-bit, 33668 * Big Endian mappings 33669 * 33670 * Copyright © 2014-2015, JEDLSoft 33671 * 33672 * Licensed under the Apache License, Version 2.0 (the "License"); 33673 * you may not use this file except in compliance with the License. 33674 * You may obtain a copy of the License at 33675 * 33676 * http://www.apache.org/licenses/LICENSE-2.0 33677 * 33678 * Unless required by applicable law or agreed to in writing, software 33679 * distributed under the License is distributed on an "AS IS" BASIS, 33680 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33681 * 33682 * See the License for the specific language governing permissions and 33683 * limitations under the License. 33684 */ 33685 33686 // !depends Charmap.js 33687 33688 // !data charset/UTF-16 charset/UTF-16BE 33689 33690 33691 /** 33692 * @class 33693 * Create a new UTF-16BE mapping instance 33694 * @constructor 33695 * @extends Charmap 33696 */ 33697 var UTF16BE = function (options) { 33698 this.charset = new Charset({name: "UTF-16BE"}); 33699 }; 33700 33701 UTF16BE.prototype = new Charmap(); 33702 UTF16BE.prototype.parent = Charmap; 33703 UTF16BE.prototype.constructor = UTF16BE; 33704 33705 UTF16BE.prototype.mapToUnicode = function (bytes) { 33706 // nodejs can't convert big-endian in native code, 33707 // so we would have to flip each Uint16 ourselves. 33708 // At that point, it's just quicker to convert 33709 // in JS code anyways 33710 var ret = ""; 33711 for (var i = 0; i < bytes.length; i += 2) { 33712 ret += String.fromCharCode(bytes[i] << 8 | bytes[i+1]); 33713 } 33714 33715 return ret; 33716 }; 33717 33718 UTF16BE.prototype.mapToNative = function(str) { 33719 // nodejs can't convert big-endian in native code, 33720 // so we would have to flip each Uint16 ourselves. 33721 // At that point, it's just quicker to convert 33722 // in JS code anyways 33723 var ret = new Uint8Array(str.length * 2 + 2); 33724 var c; 33725 for (var i = 0; i < str.length; i++) { 33726 c = str.charCodeAt(i); 33727 ret[i*2] = (c >> 8) & 0xFF; 33728 ret[i*2+1] = c & 0xFF; 33729 } 33730 // double null terminate it, just in case 33731 ret[i*2+1] = 0; 33732 ret[i*2+2] = 0; 33733 33734 return ret; 33735 }; 33736 33737 Charmap._algorithms["UTF-16BE"] = UTF16BE; 33738 33739 33740 /*< UTF16LE.js */ 33741 /* 33742 * UTF16LE.js - Implement Unicode Transformation Format 16 bit, 33743 * Little Endian mappings 33744 * 33745 * Copyright © 2014-2015, JEDLSoft 33746 * 33747 * Licensed under the Apache License, Version 2.0 (the "License"); 33748 * you may not use this file except in compliance with the License. 33749 * You may obtain a copy of the License at 33750 * 33751 * http://www.apache.org/licenses/LICENSE-2.0 33752 * 33753 * Unless required by applicable law or agreed to in writing, software 33754 * distributed under the License is distributed on an "AS IS" BASIS, 33755 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33756 * 33757 * See the License for the specific language governing permissions and 33758 * limitations under the License. 33759 */ 33760 33761 // !depends Charmap.js 33762 33763 // !data charset/UTF-16 charset/UTF-16LE 33764 33765 33766 /** 33767 * @class 33768 * Create a new UTF-16LE mapping instance 33769 * @constructor 33770 * @extends Charmap 33771 */ 33772 var UTF16LE = function (options) { 33773 this.charset = new Charset({name: "UTF-16LE"}); 33774 }; 33775 33776 UTF16LE.prototype = new Charmap(); 33777 UTF16LE.prototype.parent = Charmap; 33778 UTF16LE.prototype.constructor = UTF16LE; 33779 33780 UTF16LE.prototype.mapToUnicode = function (bytes) { 33781 if (typeof(Buffer) !== "undefined") { 33782 // nodejs can convert it quickly in native code 33783 var b = new Buffer(bytes); 33784 return b.toString("utf16le"); 33785 } 33786 // otherwise we have to implement it in pure JS 33787 var ret = ""; 33788 for (var i = 0; i < bytes.length; i += 2) { 33789 ret += String.fromCharCode(bytes[i+1] << 8 | bytes[i]); 33790 } 33791 33792 return ret; 33793 }; 33794 33795 UTF16LE.prototype.mapToNative = function(str) { 33796 if (typeof(Buffer) !== "undefined") { 33797 // nodejs can convert it quickly in native code 33798 var b = new Buffer(str, "utf16le"); 33799 return new Uint8Array(b); 33800 } 33801 // otherwise we have to implement it in pure JS 33802 var ret = new Uint8Array(str.length * 2 + 2); 33803 var c; 33804 for (var i = 0; i < str.length; i++) { 33805 c = str.charCodeAt(i); 33806 ret[i*2] = c & 0xFF; 33807 ret[i*2+1] = (c >> 8) & 0xFF; 33808 } 33809 // double null terminate it, just in case 33810 ret[i*2+1] = 0; 33811 ret[i*2+2] = 0; 33812 33813 return ret; 33814 }; 33815 33816 Charmap._algorithms["UTF-16"] = UTF16LE; 33817 Charmap._algorithms["UTF-16LE"] = UTF16LE; 33818 33819 33820 /*< /mnt/Terasaur/root/home/edwin/src/ilib-sf-trunk/js/lib/ilib-full-inc.js */ 33821 /** 33822 * @license 33823 * Copyright © 2012-2015, JEDLSoft 33824 * 33825 * Licensed under the Apache License, Version 2.0 (the "License"); 33826 * you may not use this file except in compliance with the License. 33827 * You may obtain a copy of the License at 33828 * 33829 * http://www.apache.org/licenses/LICENSE-2.0 33830 * 33831 * Unless required by applicable law or agreed to in writing, software 33832 * distributed under the License is distributed on an "AS IS" BASIS, 33833 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33834 * 33835 * See the License for the specific language governing permissions and 33836 * limitations under the License. 33837 */ 33838 33839 /* 33840 * ilib-full-inc.js - metafile that includes all other js files 33841 */ 33842 33843 /* !depends 33844 ilib.js 33845 DateRngFmt.js 33846 IDate.js 33847 DateFactory.js 33848 HebrewDate.js 33849 HebrewCal.js 33850 IslamicCal.js 33851 IslamicDate.js 33852 JulianCal.js 33853 JulianDate.js 33854 GregorianCal.js 33855 GregorianDate.js 33856 ThaiSolarCal.js 33857 ThaiSolarDate.js 33858 PersianCal.js 33859 PersianDate.js 33860 PersianAlgoCal.js 33861 PersianAlgoDate.js 33862 HanCal.js 33863 HanDate.js 33864 EthiopicCal.js 33865 EthiopicDate.js 33866 CopticCal.js 33867 CopticDate.js 33868 INumber.js 33869 NumFmt.js 33870 JulianDay.js 33871 DateFmt.js 33872 Calendar.js 33873 CalendarFactory.js 33874 Utils.js 33875 Locale.js 33876 IString.js 33877 DurationFmt.js 33878 ResBundle.js 33879 CType.js 33880 LocaleInfo.js 33881 DateRngFmt.js 33882 isAlnum.js 33883 isAlpha.js 33884 isAscii.js 33885 isBlank.js 33886 isCntrl.js 33887 isDigit.js 33888 isGraph.js 33889 isIdeo.js 33890 isLower.js 33891 isPrint.js 33892 isPunct.js 33893 isSpace.js 33894 isUpper.js 33895 isXdigit.js 33896 isScript.js 33897 ScriptInfo.js 33898 Name.js 33899 NameFmt.js 33900 Address.js 33901 AddressFmt.js 33902 Collator.js 33903 nfkc/all.js 33904 LocaleMatcher.js 33905 NormString.js 33906 CaseMapper.js 33907 GlyphString.js 33908 PhoneFmt.js 33909 PhoneGeoLocator.js 33910 PhoneNumber.js 33911 Measurement.js 33912 MeasurementFactory.js 33913 UnitFmt.js 33914 LengthUnit.js 33915 VelocityUnit.js 33916 DigitalStorageUnit.js 33917 TemperatureUnit.js 33918 UnknownUnit.js 33919 TimeUnit.js 33920 MassUnit.js 33921 AreaUnit.js 33922 FuelConsumptionUnit.js 33923 VolumeUnit.js 33924 EnergyUnit.js 33925 Charset.js 33926 Charmap.js 33927 CharmapFactory.js 33928 CharmapTable.js 33929 UTF8.js 33930 UTF16BE.js 33931 UTF16LE.js 33932 */ 33933 33934